refactor(core): вынести JSON-эквивалентность в core/jsonutil (+реэкспорт в resources_core)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Package jsonutil — нейтральный пакет каноничного JSON-сравнения.
|
||||
//
|
||||
// Вынесен из resources_core, чтобы его мог импортировать core (иначе был бы
|
||||
// цикл импорта: core → resources_core → core). Сравнивает JSON-строки,
|
||||
// игнорируя пробелы и порядок ключей объектов (порядок элементов массивов сохраняется).
|
||||
package jsonutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSONStringsEquivalent compares JSON strings ignoring whitespace and map key order.
|
||||
func JSONStringsEquivalent(a, b string) bool {
|
||||
na, oka := normalizeJSONIfPossible(a)
|
||||
if !oka {
|
||||
return false
|
||||
}
|
||||
nb, okb := normalizeJSONIfPossible(b)
|
||||
if !okb {
|
||||
return false
|
||||
}
|
||||
return na == nb
|
||||
}
|
||||
|
||||
func normalizeJSONIfPossible(value string) (string, bool) {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return "", false
|
||||
}
|
||||
var v interface{}
|
||||
if err := json.Unmarshal([]byte(trimmed), &v); err != nil {
|
||||
return "", false
|
||||
}
|
||||
return encodeCanonicalJSON(normalizeJSONScalarsToStrings(v)), true
|
||||
}
|
||||
|
||||
func encodeCanonicalJSON(v interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
writeCanonicalJSON(&buf, v)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func writeCanonicalJSON(buf *bytes.Buffer, v interface{}) {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
keys := make([]string, 0, len(val))
|
||||
for k := range val {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf.WriteByte('{')
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(strconv.Quote(k))
|
||||
buf.WriteByte(':')
|
||||
writeCanonicalJSON(buf, val[k])
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
case []interface{}:
|
||||
buf.WriteByte('[')
|
||||
for i, item := range val {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
writeCanonicalJSON(buf, item)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
case string:
|
||||
buf.WriteString(strconv.Quote(val))
|
||||
case float64:
|
||||
buf.WriteString(strconv.FormatFloat(val, 'g', -1, 64))
|
||||
case bool:
|
||||
if val {
|
||||
buf.WriteString("true")
|
||||
} else {
|
||||
buf.WriteString("false")
|
||||
}
|
||||
case nil:
|
||||
buf.WriteString("null")
|
||||
default:
|
||||
if encoded, err := json.Marshal(val); err == nil {
|
||||
buf.Write(encoded)
|
||||
} else {
|
||||
buf.WriteString("null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeJSONScalarsToStrings(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
out := make(map[string]interface{}, len(val))
|
||||
for k, child := range val {
|
||||
out[k] = normalizeJSONScalarsToStrings(child)
|
||||
}
|
||||
return out
|
||||
case []interface{}:
|
||||
out := make([]interface{}, len(val))
|
||||
for i, child := range val {
|
||||
out[i] = normalizeJSONScalarsToStrings(child)
|
||||
}
|
||||
return out
|
||||
case string:
|
||||
return val
|
||||
case float64:
|
||||
return strconv.FormatFloat(val, 'g', -1, 64)
|
||||
case bool:
|
||||
if val {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case nil:
|
||||
return "null"
|
||||
default:
|
||||
if encoded, err := json.Marshal(val); err == nil {
|
||||
return strings.TrimSpace(string(encoded))
|
||||
}
|
||||
return "null"
|
||||
}
|
||||
}
|
||||
@@ -1,121 +1,12 @@
|
||||
package resources_core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
import "terraform-provider-nubes/internal/core/jsonutil"
|
||||
|
||||
// JSONStringsEquivalent compares JSON strings ignoring whitespace and map key order.
|
||||
//
|
||||
// Реэкспорт каноничной реализации из core/jsonutil: функции вынесены в нейтральный
|
||||
// пакет, чтобы их мог импортировать и core (без цикла core → resources_core → core).
|
||||
// Оставляем обёртку, чтобы не трогать существующие вызовы в resources_core.
|
||||
func JSONStringsEquivalent(a, b string) bool {
|
||||
na, oka := normalizeJSONIfPossible(a)
|
||||
if !oka {
|
||||
return false
|
||||
}
|
||||
nb, okb := normalizeJSONIfPossible(b)
|
||||
if !okb {
|
||||
return false
|
||||
}
|
||||
return na == nb
|
||||
}
|
||||
|
||||
func normalizeJSONIfPossible(value string) (string, bool) {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return "", false
|
||||
}
|
||||
var v interface{}
|
||||
if err := json.Unmarshal([]byte(trimmed), &v); err != nil {
|
||||
return "", false
|
||||
}
|
||||
return encodeCanonicalJSON(normalizeJSONScalarsToStrings(v)), true
|
||||
}
|
||||
|
||||
func encodeCanonicalJSON(v interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
writeCanonicalJSON(&buf, v)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func writeCanonicalJSON(buf *bytes.Buffer, v interface{}) {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
keys := make([]string, 0, len(val))
|
||||
for k := range val {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf.WriteByte('{')
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(strconv.Quote(k))
|
||||
buf.WriteByte(':')
|
||||
writeCanonicalJSON(buf, val[k])
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
case []interface{}:
|
||||
buf.WriteByte('[')
|
||||
for i, item := range val {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
writeCanonicalJSON(buf, item)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
case string:
|
||||
buf.WriteString(strconv.Quote(val))
|
||||
case float64:
|
||||
buf.WriteString(strconv.FormatFloat(val, 'g', -1, 64))
|
||||
case bool:
|
||||
if val {
|
||||
buf.WriteString("true")
|
||||
} else {
|
||||
buf.WriteString("false")
|
||||
}
|
||||
case nil:
|
||||
buf.WriteString("null")
|
||||
default:
|
||||
if encoded, err := json.Marshal(val); err == nil {
|
||||
buf.Write(encoded)
|
||||
} else {
|
||||
buf.WriteString("null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeJSONScalarsToStrings(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
out := make(map[string]interface{}, len(val))
|
||||
for k, child := range val {
|
||||
out[k] = normalizeJSONScalarsToStrings(child)
|
||||
}
|
||||
return out
|
||||
case []interface{}:
|
||||
out := make([]interface{}, len(val))
|
||||
for i, child := range val {
|
||||
out[i] = normalizeJSONScalarsToStrings(child)
|
||||
}
|
||||
return out
|
||||
case string:
|
||||
return val
|
||||
case float64:
|
||||
return strconv.FormatFloat(val, 'g', -1, 64)
|
||||
case bool:
|
||||
if val {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case nil:
|
||||
return "null"
|
||||
default:
|
||||
if encoded, err := json.Marshal(val); err == nil {
|
||||
return strings.TrimSpace(string(encoded))
|
||||
}
|
||||
return "null"
|
||||
}
|
||||
return jsonutil.JSONStringsEquivalent(a, b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user