2021-10-25 10:36:58 -07:00
|
|
|
package patchstruct
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
|
)
|
|
|
|
|
|
2021-11-26 13:58:19 -08:00
|
|
|
// PatchStruct updates the struct found in dst with the values found in src. The
|
|
|
|
|
// intent of this helper is to provide a fallback mechanism for subtype
|
2021-10-25 10:36:58 -07:00
|
|
|
// attributes when the actual schema of the subtype attributes are unknown. As
|
|
|
|
|
// such, it's preferred to use other methods (such as mask mapping) when an
|
|
|
|
|
// actual message for the subtype is known.
|
|
|
|
|
//
|
|
|
|
|
// The following rules apply:
|
|
|
|
|
//
|
2022-08-25 09:12:24 -07:00
|
|
|
// - The source (src) map is merged into the destination map (dst). If the
|
|
|
|
|
// source map is nil, the destination will be a valid, but empty, map.
|
2021-10-25 10:36:58 -07:00
|
|
|
//
|
2022-08-25 09:12:24 -07:00
|
|
|
// - Values are overwritten by the source map if they exist in both.
|
2021-10-25 10:36:58 -07:00
|
|
|
//
|
2022-08-25 09:12:24 -07:00
|
|
|
// - Values are deleted from the destination if they are set to null in the
|
|
|
|
|
// source.
|
2021-10-25 10:36:58 -07:00
|
|
|
//
|
2022-08-25 09:12:24 -07:00
|
|
|
// - Maps are recursively applied, meaning that a nested map at key "foo" in
|
|
|
|
|
// the destination would be patched with a map at key "foo" in the
|
|
|
|
|
// source.
|
2021-10-25 10:36:58 -07:00
|
|
|
//
|
2022-08-25 09:12:24 -07:00
|
|
|
// - A map in the destination is overwritten by a non-map in the source,
|
|
|
|
|
// and a non-map in the destination is overwritten by a map in the
|
|
|
|
|
// source.
|
2021-10-25 10:36:58 -07:00
|
|
|
//
|
|
|
|
|
// PatchStruct returns the updated map as a copy, dst and src are not altered.
|
|
|
|
|
func PatchStruct(dst, src *structpb.Struct) *structpb.Struct {
|
2021-11-26 13:58:19 -08:00
|
|
|
if src == nil {
|
|
|
|
|
ret, _ := structpb.NewStruct(nil)
|
|
|
|
|
return ret
|
|
|
|
|
}
|
2021-10-25 10:36:58 -07:00
|
|
|
result, err := structpb.NewStruct(patchM(dst.AsMap(), src.AsMap()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Should never error as values are source from structpb values
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 13:58:19 -08:00
|
|
|
// PatchBytes follows the same rules as above with PatchStruct, but instead of
|
|
|
|
|
// patching structpb.Structs, it patches the protobuf encoding. An error is
|
|
|
|
|
// returned if there are issues working with the data. If src is nil or empty,
|
|
|
|
|
// the result is a marshaled, empty struct.
|
2021-10-25 10:36:58 -07:00
|
|
|
func PatchBytes(dst, src []byte) ([]byte, error) {
|
|
|
|
|
srcpb, dstpb := new(structpb.Struct), new(structpb.Struct)
|
2021-11-26 13:58:19 -08:00
|
|
|
if len(src) != 0 {
|
|
|
|
|
if err := proto.Unmarshal(dst, dstpb); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error reading destination data: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := proto.Unmarshal(src, srcpb); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error reading source data: %w", err)
|
|
|
|
|
}
|
|
|
|
|
dstpb = PatchStruct(dstpb, srcpb)
|
|
|
|
|
} else {
|
|
|
|
|
dstpb, _ = structpb.NewStruct(nil)
|
2021-10-25 10:36:58 -07:00
|
|
|
}
|
|
|
|
|
|
2021-11-26 13:58:19 -08:00
|
|
|
result, err := proto.Marshal(dstpb)
|
2021-10-25 10:36:58 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error writing result data: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 18:29:38 -05:00
|
|
|
func patchM(dst, src map[string]any) map[string]any {
|
2021-10-25 10:36:58 -07:00
|
|
|
for k, v := range src {
|
|
|
|
|
switch x := v.(type) {
|
2022-10-25 18:29:38 -05:00
|
|
|
case map[string]any:
|
|
|
|
|
if y, ok := dst[k].(map[string]any); ok {
|
2021-10-25 10:36:58 -07:00
|
|
|
// If the value in dst a map, continue to patch
|
|
|
|
|
dst[k] = patchM(y, x)
|
|
|
|
|
} else {
|
2021-11-09 14:42:48 -07:00
|
|
|
// Overwrite after stripping out keys to nil values
|
2022-10-25 18:29:38 -05:00
|
|
|
newX := patchM(make(map[string]any), x)
|
2021-11-09 14:42:48 -07:00
|
|
|
dst[k] = newX
|
2021-10-25 10:36:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
if v == nil {
|
|
|
|
|
// explicit null values delete values at that key in dst
|
|
|
|
|
delete(dst, k)
|
|
|
|
|
} else {
|
|
|
|
|
// Anything else gets overwritten
|
|
|
|
|
dst[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dst
|
|
|
|
|
}
|