diff --git a/bundle/manifests/oran-o2ims.clusterserviceversion.yaml b/bundle/manifests/oran-o2ims.clusterserviceversion.yaml index 596b8c93..29636663 100644 --- a/bundle/manifests/oran-o2ims.clusterserviceversion.yaml +++ b/bundle/manifests/oran-o2ims.clusterserviceversion.yaml @@ -39,9 +39,8 @@ metadata: "extra-label-key": "extra-label-value" }, "ManagedCluster": { - "common": "true", - "group-du-sno": "test", - "sites": "site-sno-du-1" + "cluster-version": "v4.16", + "clustertemplate-a-policy": "v1" } }, "ingressVIPs": [ diff --git a/config/samples/v1alpha1_clusterrequest.yaml b/config/samples/v1alpha1_clusterrequest.yaml index e35d5c52..56a268a0 100644 --- a/config/samples/v1alpha1_clusterrequest.yaml +++ b/config/samples/v1alpha1_clusterrequest.yaml @@ -34,9 +34,8 @@ spec: AgentClusterInstall: extra-label-key: extra-label-value ManagedCluster: - common: "true" - group-du-sno: test - sites: site-sno-du-1 + cluster-version: "v4.16" + clustertemplate-a-policy: "v1" ingressVIPs: - 192.0.2.3 machineNetwork: diff --git a/go.mod b/go.mod index 6e917020..794f0cda 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/openshift/assisted-service/api v0.0.0-20240405132132-484ec5c683c6 github.com/peterhellberg/link v1.2.0 github.com/prometheus/client_golang v1.18.0 + github.com/r3labs/diff/v3 v3.0.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace github.com/stolostron/siteconfig v0.0.0-20240911204707-0bba68d7dcc0 @@ -94,6 +95,8 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect diff --git a/go.sum b/go.sum index cbcbcfb3..b8ebf2b6 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= +github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -360,6 +362,10 @@ github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= diff --git a/internal/controllers/clusterrequest_controller.go b/internal/controllers/clusterrequest_controller.go index b4b51e7c..6cc22f68 100644 --- a/internal/controllers/clusterrequest_controller.go +++ b/internal/controllers/clusterrequest_controller.go @@ -46,6 +46,7 @@ import ( oranv1alpha1 "github.com/openshift-kni/oran-o2ims/api/v1alpha1" "github.com/openshift-kni/oran-o2ims/internal/controllers/utils" siteconfig "github.com/stolostron/siteconfig/api/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" clusterv1 "open-cluster-management.io/api/cluster/v1" policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -524,10 +525,53 @@ func (t *clusterRequestReconcilerTask) renderClusterInstanceTemplate( labels[clusterRequestNamespaceLabel] = t.object.Namespace renderedClusterInstanceUnstructure.SetLabels(labels) - // Create the ClusterInstance namespace. - err = t.createClusterInstanceNamespace(ctx, renderedClusterInstanceUnstructure.GetName()) + // Create the ClusterInstance namespace if not exist. + ciName := renderedClusterInstanceUnstructure.GetName() + err = t.createClusterInstanceNamespace(ctx, ciName) if err != nil { - return nil, fmt.Errorf("failed to create cluster namespace %s: %w", renderedClusterInstanceUnstructure.GetName(), err) + return nil, fmt.Errorf("failed to create cluster namespace %s: %w", ciName, err) + } + + // Check for updates to immutable fields in the ClusterInstance, if it exists. + // Once provisioning has started or reached a final state (Completed or Failed), + // updates to immutable fields in the ClusterInstance spec are disallowed, + // with the exception of scaling up/down when Cluster provisioning is completed. + crProvisionedCond := meta.FindStatusCondition(t.object.Status.Conditions, + string(utils.CRconditionTypes.ClusterProvisioned)) + if crProvisionedCond != nil && crProvisionedCond.Reason != string(utils.CRconditionReasons.Unknown) { + existingClusterInstance := &unstructured.Unstructured{} + existingClusterInstance.SetGroupVersionKind( + renderedClusterInstanceUnstructure.GroupVersionKind()) + ciExists, err := utils.DoesK8SResourceExist( + ctx, t.client, ciName, ciName, existingClusterInstance, + ) + if err != nil { + return nil, fmt.Errorf("failed to get ClusterInstance (%s): %w", + ciName, err) + } + if ciExists { + updatedFields, scalingNodes, err := utils.FindClusterInstanceImmutableFieldUpdates( + existingClusterInstance, renderedClusterInstanceUnstructure) + if err != nil { + return nil, fmt.Errorf( + "failed to find immutable field updates for ClusterInstance (%s): %w", ciName, err) + } + + var disallowedChanges []string + if len(updatedFields) != 0 { + disallowedChanges = append(disallowedChanges, updatedFields...) + } + if len(scalingNodes) != 0 && + crProvisionedCond.Reason != string(utils.CRconditionReasons.Completed) { + // In-progress || Failed + disallowedChanges = append(disallowedChanges, scalingNodes...) + } + + if len(disallowedChanges) != 0 { + return nil, utils.NewInputError(fmt.Sprintf( + "detected changes in immutable fields: %s", strings.Join(disallowedChanges, ", "))) + } + } } // Validate the rendered ClusterInstance with dry-run @@ -750,8 +794,13 @@ func (t *clusterRequestReconcilerTask) applyClusterInstance(ctx context.Context, return utils.NewInputError(err.Error()) } } else { - // TODO: only update the existing clusterInstance when a list of allowed fields are changed - // TODO: What about if the ClusterInstance is not generated by a ClusterRequest? + if _, ok := clusterInstance.(*siteconfig.ClusterInstance); ok { + // No update needed, return + if equality.Semantic.DeepEqual(existingClusterInstance.Spec, + clusterInstance.(*siteconfig.ClusterInstance).Spec) { + return nil + } + } // Make sure these fields from existing object are copied clusterInstance.SetResourceVersion(existingClusterInstance.GetResourceVersion()) @@ -1277,7 +1326,7 @@ func (t *clusterRequestReconcilerTask) createClusterInstanceNamespace( labels[clusterRequestNamespaceLabel] = t.object.Namespace namespace.SetLabels(labels) - err := utils.CreateK8sCR(ctx, t.client, namespace, nil, utils.UPDATE) + err := utils.CreateK8sCR(ctx, t.client, namespace, nil, "") if err != nil { return fmt.Errorf("failed to create or update namespace %s: %w", clusterName, err) } @@ -1643,7 +1692,7 @@ func (r *ClusterRequestReconciler) handleFinalizer( return ctrl.Result{}, true, err } - // Remove clusterInstanceFinalizer. Once all finalizers have been + // Remove clusterRequestFinalizer. Once all finalizers have been // removed, the object will be deleted. r.Logger.Info("Removing ClusterRequest finalizer", "name", clusterRequest.Name) patch := client.MergeFrom(clusterRequest.DeepCopy()) diff --git a/internal/controllers/clusterrequest_controller_test.go b/internal/controllers/clusterrequest_controller_test.go index 2fc66610..2eaa2989 100644 --- a/internal/controllers/clusterrequest_controller_test.go +++ b/internal/controllers/clusterrequest_controller_test.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/yaml" ) type expectedNodeDetails struct { @@ -246,13 +247,13 @@ const ( "NTP.server1" ], "apiVIPs": [ - "10.16.231.1" + "192.0.2.2" ], "baseDomain": "example.com", "clusterName": "cluster-1", "machineNetwork": [ { - "cidr": "10.16.231.0/24" + "cidr": "192.0.2.0/24" } ], "extraAnnotations": { @@ -269,11 +270,11 @@ const ( } }, "ingressVIPs": [ - "10.16.231.2" + "192.0.2.4" ], "nodes": [ { - "bmcAddress": "idrac-virtualmedia+https://10.16.231.87/redfish/v1/Systems/System.Embedded.1", + "bmcAddress": "idrac-virtualmedia+https://203.0.113.5/redfish/v1/Systems/System.Embedded.1", "bmcCredentialsName": { "name": "site-sno-du-1-bmc-secret" }, @@ -292,7 +293,7 @@ const ( "dns-resolver": { "config": { "server": [ - "10.19.42.41" + "192.0.2.22" ] } }, @@ -301,15 +302,15 @@ const ( "ipv4": { "address": [ { - "ip": "10.16.231.3", + "ip": "192.0.2.10", "prefix-length": 24 }, { - "ip": "10.16.231.28", + "ip": "192.0.2.11", "prefix-length": 24 }, { - "ip": "10.16.231.31", + "ip": "192.0.2.12", "prefix-length": 24 } ], @@ -319,16 +320,16 @@ const ( "ipv6": { "address": [ { - "ip": "2620:52:0:10e7:e42:a1ff:fe8a:601", - "prefix-length": 64 + "ip": "2001:db8:0:1::42", + "prefix-length": 32 }, { - "ip": "2620:52:0:10e7:e42:a1ff:fe8a:602", - "prefix-length": 64 + "ip": "2001:db8:0:1::43", + "prefix-length": 32 }, { - "ip": "2620:52:0:10e7:e42:a1ff:fe8a:603", - "prefix-length": 64 + "ip": "2001:db8:0:1::44", + "prefix-length": 32 } ], "dhcp": false, @@ -341,7 +342,7 @@ const ( "ipv6": { "address": [ { - "ip": "2620:52:0:1302::100" + "ip": "2001:db8:abcd:1234::1" } ], "enabled": true, @@ -366,7 +367,7 @@ const ( "config": [ { "destination": "0.0.0.0/0", - "next-hop-address": "10.16.231.254", + "next-hop-address": "192.0.2.254", "next-hop-interface": "eno1", "table-id": 254 } @@ -392,7 +393,7 @@ const ( ], "serviceNetwork": [ { - "cidr": "172.30.0.0/16" + "cidr": "233.252.0.0/24" } ], "sshPublicKey": "ssh-rsa " @@ -1258,6 +1259,175 @@ var _ = Describe("getCrClusterTemplateRef", func() { }) }) +var _ = Describe("handleRenderClusterInstance", func() { + var ( + ctx context.Context + c client.Client + reconciler *ClusterRequestReconciler + task *clusterRequestReconcilerTask + cr *oranv1alpha1.ClusterRequest + ciDefaultsCm = "clusterinstance-defaults-v1" + ctName = "clustertemplate-a-v1" + ctNamespace = "clustertemplate-a-v4-16" + crName = "cluster-1" + ) + + BeforeEach(func() { + ctx = context.Background() + // Define the cluster request. + cr = &oranv1alpha1.ClusterRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: ctNamespace, + }, + Spec: oranv1alpha1.ClusterRequestSpec{ + ClusterTemplateRef: ctName, + ClusterTemplateInput: oranv1alpha1.ClusterTemplateInput{ + ClusterInstanceInput: runtime.RawExtension{Raw: []byte(testClusterTemplateInput)}, + }, + }, + } + + // Define the cluster template. + ct := &oranv1alpha1.ClusterTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: ctName, + Namespace: ctNamespace, + }, + Spec: oranv1alpha1.ClusterTemplateSpec{ + Templates: oranv1alpha1.Templates{ + ClusterInstanceDefaults: ciDefaultsCm, + }, + }, + } + // Configmap for ClusterInstance defaults + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ciDefaultsCm, + Namespace: ctNamespace, + }, + Data: map[string]string{ + utils.ClusterInstanceTemplateDefaultsConfigmapKey: ` +clusterImageSetNameRef: "4.15" +pullSecretRef: + name: "pull-secret" +holdInstallation: false +templateRefs: + - name: "ai-cluster-templates-v1" + namespace: "siteconfig-operator" +nodes: +- hostname: "node1" + ironicInspect: "" + templateRefs: + - name: "ai-node-templates-v1" + namespace: "siteconfig-operator"`, + }, + } + + c = getFakeClientFromObjects([]client.Object{cr, ct, cm}...) + reconciler = &ClusterRequestReconciler{ + Client: c, + Logger: logger, + } + task = &clusterRequestReconcilerTask{ + logger: reconciler.Logger, + client: reconciler.Client, + object: cr, + clusterInput: &clusterInput{}, + } + + mergedClusterInstanceData, err := task.getMergedClusterInputData(ctx, ct, utils.ClusterInstanceDataType) + Expect(err).ToNot(HaveOccurred()) + task.clusterInput.clusterInstanceData = mergedClusterInstanceData + }) + + It("should successfully render and validate ClusterInstance with dry-run", func() { + renderedClusterInstance, err := task.handleRenderClusterInstance(ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(renderedClusterInstance).ToNot(BeNil()) + + // Check if status condition was updated correctly + cond := meta.FindStatusCondition(task.object.Status.Conditions, + string(utils.CRconditionTypes.ClusterInstanceRendered)) + Expect(cond).ToNot(BeNil()) + verifyStatusCondition(*cond, metav1.Condition{ + Type: string(utils.CRconditionTypes.ClusterInstanceRendered), + Status: metav1.ConditionTrue, + Reason: string(utils.CRconditionReasons.Completed), + Message: "ClusterInstance rendered and passed dry-run validation", + }) + }) + + It("should fail to render ClusterInstance due to invalid input", func() { + // Modify input data to be invalid + task.clusterInput.clusterInstanceData["clusterName"] = "" + _, err := task.handleRenderClusterInstance(ctx) + Expect(err).To(HaveOccurred()) + + // Check if status condition was updated correctly + cond := meta.FindStatusCondition(task.object.Status.Conditions, + string(utils.CRconditionTypes.ClusterInstanceRendered)) + Expect(cond).ToNot(BeNil()) + verifyStatusCondition(*cond, metav1.Condition{ + Type: string(utils.CRconditionTypes.ClusterInstanceRendered), + Status: metav1.ConditionFalse, + Reason: string(utils.CRconditionReasons.Failed), + Message: "spec.clusterName cannot be empty", + }) + }) + + It("should detect updates to immutable fields and fail rendering", func() { + // Simulate that the ClusterInstance has been provisioned + task.object.Status.Conditions = []metav1.Condition{ + { + Type: string(utils.CRconditionTypes.ClusterProvisioned), + Status: metav1.ConditionTrue, + Reason: string(utils.CRconditionReasons.Completed), + }, + } + + oldSpec := make(map[string]any) + newSpec := make(map[string]any) + data, err := yaml.Marshal(task.clusterInput.clusterInstanceData) + Expect(err).ToNot(HaveOccurred()) + Expect(yaml.Unmarshal(data, &oldSpec)).To(Succeed()) + Expect(yaml.Unmarshal(data, &newSpec)).To(Succeed()) + + clusterInstanceObj := map[string]any{ + "Cluster": task.clusterInput.clusterInstanceData, + } + oldClusterInstance, err := utils.RenderTemplateForK8sCR( + utils.ClusterInstanceTemplateName, utils.ClusterInstanceTemplatePath, clusterInstanceObj) + Expect(err).ToNot(HaveOccurred()) + Expect(c.Create(ctx, oldClusterInstance)).To(Succeed()) + + // Update the cluster data with modified field + // Change an immutable field at the cluster-level + newSpec["baseDomain"] = "newdomain.example.com" + task.clusterInput.clusterInstanceData = newSpec + + _, err = task.handleRenderClusterInstance(ctx) + Expect(err).To(HaveOccurred()) + + // Note that the detected changed fields in this unittest include nodes.0.ironicInspect, baseDomain, + // and holdInstallation, even though nodes.0.ironicInspect and holdInstallation were not actually changed. + // This is due to the difference between the fakeclient and a real cluster. When applying a manifest + // to a cluster, the API server preserves the full resource, including optional fields with empty values. + // However, the fakeclient in unittests behaves differently, as it uses an in-memory store and + // does not go through the API server. As a result, fields with empty values like false or "" are + // stripped from the retrieved ClusterInstance CR (existing ClusterInstance) in the fakeclient. + cond := meta.FindStatusCondition(task.object.Status.Conditions, + string(utils.CRconditionTypes.ClusterInstanceRendered)) + Expect(cond).ToNot(BeNil()) + verifyStatusCondition(*cond, metav1.Condition{ + Type: string(utils.CRconditionTypes.ClusterInstanceRendered), + Status: metav1.ConditionFalse, + Reason: string(utils.CRconditionReasons.Failed), + Message: "Failed to render and validate ClusterInstance: detected changes in immutable fields", + }) + }) +}) + var _ = Describe("createPolicyTemplateConfigMap", func() { var ( ctx context.Context diff --git a/internal/controllers/utils/constants.go b/internal/controllers/utils/constants.go index c323dd4b..69a43362 100644 --- a/internal/controllers/utils/constants.go +++ b/internal/controllers/utils/constants.go @@ -73,11 +73,23 @@ const ( ClusterInstanceTemplateName = "ClusterInstance" ClusterInstanceTemplatePath = "controllers/clusterinstance-template.yaml" ClusterInstanceTemplateConfigmapName = "sc-clusterinstance-template" - ClusterInstanceTemplateConfigmapNamespace = InventoryNamespace ClusterInstanceTemplateDefaultsConfigmapKey = "clusterinstance-defaults" ClusterInstanceSchema = "clusterInstanceSchema" ) +var ( + // Non-immutable ClusterInstance fields at the cluster-level + AllowedClusterInstanceClusterFields = []string{ + "extraAnnotations", + "extraLabels", + } + // Non-immutable ClusterInstance fields at the node-level + AllowedClusterInstanceNodeFields = []string{ + "extraAnnotations", + "extraLabels", + } +) + // PolicyTemplate constants const ( PolicyTemplateDefaultsConfigmapKey = "policytemplate-defaults" diff --git a/internal/controllers/utils/utils.go b/internal/controllers/utils/utils.go index d298f557..308b5120 100644 --- a/internal/controllers/utils/utils.go +++ b/internal/controllers/utils/utils.go @@ -19,6 +19,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" sprig "github.com/go-task/slim-sprig/v3" + diff "github.com/r3labs/diff/v3" "github.com/xeipuuv/gojsonschema" hwv1alpha1 "github.com/openshift-kni/oran-o2ims/api/hardwaremanagement/v1alpha1" @@ -818,6 +819,64 @@ func CheckClusterLabelsForPolicies( return nil } +// FindClusterInstanceImmutableFieldUpdates identifies updates made to immutable fields +// in the ClusterInstance spec. It returns two lists of paths: a list of updated fields +// that are considered immutable and should not be modified and a list of fields related +// to node scaling, indicating nodes that were added or removed. +func FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance *unstructured.Unstructured) ([]string, []string, error) { + + oldClusterInstanceSpec := oldClusterInstance.Object["spec"].(map[string]any) + newClusterInstanceSpec := newClusterInstance.Object["spec"].(map[string]any) + + diffs, err := diff.Diff(oldClusterInstanceSpec, newClusterInstanceSpec) + if err != nil { + return nil, nil, fmt.Errorf("error comparing differences between existing "+ + "and newly rendered ClusterInstance: %w", err) + } + + var updatedFields []string + var scalingNodes []string + for _, diff := range diffs { + /* Examples of diff result in json format + + Label added at the cluster-level + {"type": "create", "path": ["extraLabels", "ManagedCluster", "newLabelKey"], "from": null, "to": "newLabelValue"} + + Field updated at the cluster-level + {"type": "update", "path": ["baseDomain"], "from": "domain.example.com", "to": "newdomain.example.com"} + + New node added + {"type": "create", "path": ["nodes", "1"], "from": null, "to": {"hostName": "worker2"}} + + Existing node removed + {"type": "delete", "path": ["nodes", "1"], "from": {"hostName": "worker2"}, "to": null} + + Field updated at the node-level + {"type": "update", "path": ["nodes", "0", "nodeNetwork", "config", "dns-resolver", "config", "server", "0"], "from": "192.10.1.2", "to": "192.10.1.3"} + */ + + oranUtilsLog.Info( + fmt.Sprintf( + "Detected field change in the newly rendered ClusterInstance(%s) type: %s, path: %s, from: %+v, to: %+v", + oldClusterInstance.GetName(), diff.Type, strings.Join(diff.Path, "."), diff.From, diff.To, + ), + ) + + if diff.Path[0] == "nodes" { + if len(diff.Path) == 2 { + scalingNodes = append(scalingNodes, strings.Join(diff.Path, ".")) + } else if len(diff.Path) > 2 && !slices.Contains(AllowedClusterInstanceNodeFields, diff.Path[2]) { + updatedFields = append(updatedFields, strings.Join(diff.Path, ".")) + } + } else if !slices.Contains(AllowedClusterInstanceClusterFields, diff.Path[0]) { + updatedFields = append(updatedFields, strings.Join(diff.Path, ".")) + } + } + + return updatedFields, scalingNodes, nil +} + // GetTLSSkipVerify returns the current requested value of the TLS Skip Verify setting func GetTLSSkipVerify() bool { value, ok := os.LookupEnv(TLSSkipVerifyEnvName) diff --git a/internal/controllers/utils/utils_test.go b/internal/controllers/utils/utils_test.go index ffaba68b..d141be75 100644 --- a/internal/controllers/utils/utils_test.go +++ b/internal/controllers/utils/utils_test.go @@ -859,121 +859,127 @@ nodes: }) }) -var _ = Describe("RenderTemplateForK8sCR", func() { - var ( - clusterInstanceObj map[string]interface{} - expectedRenderedYaml string - ) - - BeforeEach(func() { - clusterInstanceObj = map[string]interface{}{ - "Cluster": map[string]interface{}{ - "clusterName": "site-sno-du-1", - "baseDomain": "example.com", - "clusterImageSetNameRef": "4.16", - "pullSecretRef": map[string]interface{}{"name": "pullSecretName"}, - "templateRefs": []map[string]interface{}{{"name": "aci-cluster-crs-v1", "namespace": "siteconfig-system"}}, - "additionalNTPSources": []string{"NTP.server1", "10.16.231.22"}, - "apiVIPs": []string{"10.0.0.1", "10.0.0.2"}, - "caBundleRef": map[string]interface{}{"name": "my-bundle-ref"}, - "extraLabels": map[string]map[string]string{"ManagedCluster": {"common": "true", "group-du-sno": "test", "sites": "site-sno-du-1"}}, - "clusterType": "SNO", - "clusterNetwork": []map[string]interface{}{{"cidr": "10.128.0.0/14", "hostPrefix": 23}}, - "machineNetwork": []map[string]interface{}{{"cidr": "10.16.231.0/24"}}, - "networkType": "OVNKubernetes", - "cpuPartitioningMode": "AllNodes", - "diskEncryption": map[string]interface{}{"tang": []map[string]interface{}{{"thumbprint": "1234567890", "url": "http://10.0.0.1:7500"}}, "type": "nbde"}, - "extraManifestsRefs": []map[string]interface{}{{"name": "foobar1"}, {"name": "foobar2"}}, - "ignitionConfigOverride": "igen", - "installConfigOverrides": "{\"capabilities\":{\"baselineCapabilitySet\": \"None\", \"additionalEnabledCapabilities\": [ \"marketplace\", \"NodeTuning\" ] }}", - "proxy": map[string]interface{}{"noProxy": "foobar"}, - "serviceNetwork": []map[string]interface{}{{"cidr": "172.30.0.0/16"}}, - "sshPublicKey": "ssh-rsa", - "nodes": []map[string]interface{}{ - { - "bmcAddress": "idrac-virtualmedia+https://10.16.231.87/redfish/v1/Systems/System.Embedded.1", - "bmcCredentialsName": map[string]interface{}{"name": "node1-bmc-secret"}, - "bootMACAddress": "00:00:00:01:20:30", - "bootMode": "UEFI", - "hostName": "node1.baseDomain.com", - "ignitionConfigOverride": "{\"ignition\": {\"version\": \"3.1.0\"}, \"storage\": {\"files\": [{\"path\": \"/etc/containers/registries.conf\", \"overwrite\": true, \"contents\": {\"source\": \"data:text/plain;base64,aGVsbG8gZnJvbSB6dHAgcG9saWN5IGdlbmVyYXRvcg==\"}}]}}", - "installerArgs": "[\"--append-karg\", \"nameserver=8.8.8.8\", \"-n\"]", - "ironicInspect": "", - "role": "master", - "rootDeviceHint": map[string]interface{}{"hctl": "1:2:0:0"}, - "automatedCleaningMode": "disabled", - "templateRefs": []map[string]interface{}{{"name": "aci-node-crs-v1", "namespace": "siteconfig-system"}}, - "nodeNetwork": map[string]interface{}{ - "config": map[string]interface{}{ - "dns-resolver": map[string]interface{}{ - "config": map[string]interface{}{ - "server": []string{"10.19.42.41"}, - }, +var testClusterInstanceData = map[string]interface{}{ + "clusterName": "site-sno-du-1", + "baseDomain": "example.com", + "clusterImageSetNameRef": "4.16", + "pullSecretRef": map[string]interface{}{"name": "pullSecretName"}, + "templateRefs": []map[string]interface{}{{"name": "aci-cluster-crs-v1", "namespace": "siteconfig-system"}}, + "additionalNTPSources": []string{"NTP.server1", "1.1.1.1"}, + "apiVIPs": []string{"192.0.2.2", "192.0.2.3"}, + "caBundleRef": map[string]interface{}{"name": "my-bundle-ref"}, + "extraLabels": map[string]map[string]string{"ManagedCluster": {"cluster-version": "v4.16", "clustertemplate-a-policy": "v1"}}, + "clusterType": "SNO", + "clusterNetwork": []map[string]interface{}{{"cidr": "203.0.113.0/24", "hostPrefix": 23}}, + "machineNetwork": []map[string]interface{}{{"cidr": "192.0.2.0/24"}}, + "networkType": "OVNKubernetes", + "cpuPartitioningMode": "AllNodes", + "diskEncryption": map[string]interface{}{"tang": []map[string]interface{}{{"thumbprint": "1234567890", "url": "http://198.51.100.1:7500"}}, "type": "nbde"}, + "extraManifestsRefs": []map[string]interface{}{{"name": "foobar1"}, {"name": "foobar2"}}, + "ignitionConfigOverride": "igen", + "installConfigOverrides": "{\"capabilities\":{\"baselineCapabilitySet\": \"None\", \"additionalEnabledCapabilities\": [ \"marketplace\", \"NodeTuning\" ] }}", + "proxy": map[string]interface{}{"noProxy": "foobar"}, + "serviceNetwork": []map[string]interface{}{{"cidr": "233.252.0.0/24"}}, + "sshPublicKey": "ssh-rsa", + "nodes": []map[string]interface{}{ + { + "bmcAddress": "idrac-virtualmedia+https://203.0.113.5/redfish/v1/Systems/System.Embedded.1", + "bmcCredentialsName": map[string]interface{}{"name": "node1-bmc-secret"}, + "bootMACAddress": "00:00:00:01:20:30", + "bootMode": "UEFI", + "hostName": "node1.baseDomain.com", + "ignitionConfigOverride": "{\"ignition\": {\"version\": \"3.1.0\"}, \"storage\": {\"files\": [{\"path\": \"/etc/containers/registries.conf\", \"overwrite\": true, \"contents\": {\"source\": \"data:text/plain;base64,aGVsbG8gZnJvbSB6dHAgcG9saWN5IGdlbmVyYXRvcg==\"}}]}}", + "installerArgs": "[\"--append-karg\", \"nameserver=8.8.8.8\", \"-n\"]", + "ironicInspect": "", + "role": "master", + "rootDeviceHint": map[string]interface{}{"hctl": "1:2:0:0"}, + "automatedCleaningMode": "disabled", + "templateRefs": []map[string]interface{}{{"name": "aci-node-crs-v1", "namespace": "siteconfig-system"}}, + "nodeNetwork": map[string]interface{}{ + "config": map[string]interface{}{ + "dns-resolver": map[string]interface{}{ + "config": map[string]interface{}{ + "server": []string{"192.0.2.22"}, + }, + }, + "interfaces": []map[string]interface{}{ + { + "ipv4": map[string]interface{}{ + "address": []map[string]interface{}{ + {"ip": "192.0.2.10", "prefix-length": 24}, + {"ip": "192.0.2.11", "prefix-length": 24}, + {"ip": "192.0.2.12", "prefix-length": 24}, }, - "interfaces": []map[string]interface{}{ - { - "ipv4": map[string]interface{}{ - "address": []map[string]interface{}{ - {"ip": "10.16.231.3", "prefix-length": 24}, - {"ip": "10.16.231.28", "prefix-length": 24}, - {"ip": "10.16.231.31", "prefix-length": 24}, - }, - "dhcp": false, - "enabled": true, - }, - "ipv6": map[string]interface{}{ - "address": []map[string]interface{}{ - {"ip": "2620:52:0:10e7:e42:a1ff:fe8a:601", "prefix-length": 64}, - {"ip": "2620:52:0:10e7:e42:a1ff:fe8a:602", "prefix-length": 64}, - {"ip": "2620:52:0:10e7:e42:a1ff:fe8a:603", "prefix-length": 64}, - }, - "dhcp": false, - "enabled": true, - }, - "name": "eno1", - "type": "ethernet", - }, - { - "ipv6": map[string]interface{}{ - "address": []map[string]interface{}{ - {"ip": "2620:52:0:1302::100"}, - }, - "enabled": true, - "link-aggregation": map[string]interface{}{ - "mode": "balance-rr", - "options": map[string]interface{}{ - "miimon": "140", - }, - "slaves": []string{"eth0", "eth1"}, - }, - "prefix-length": 64, - }, - "name": "bond99", - "state": "up", - "type": "bond", - }, + "dhcp": false, + "enabled": true, + }, + "ipv6": map[string]interface{}{ + "address": []map[string]interface{}{ + {"ip": "2001:db8:0:1::42", "prefix-length": 32}, + {"ip": "2001:db8:0:1::43", "prefix-length": 32}, + {"ip": "2001:db8:0:1::44", "prefix-length": 32}, }, - "routes": map[string]interface{}{ - "config": []map[string]interface{}{ - { - "destination": "0.0.0.0/0", - "next-hop-address": "10.16.231.254", - "next-hop-interface": "eno1", - "table": "", - }, + "dhcp": false, + "enabled": true, + }, + "name": "eno1", + "type": "ethernet", + }, + { + "ipv6": map[string]interface{}{ + "address": []map[string]interface{}{ + {"ip": "2001:db8:abcd:1234::1"}, + }, + "enabled": true, + "link-aggregation": map[string]interface{}{ + "mode": "balance-rr", + "options": map[string]interface{}{ + "miimon": "140", }, + "slaves": []string{"eth0", "eth1"}, }, + "prefix-length": 32, }, - "interfaces": []map[string]interface{}{ - {"macAddress": "00:00:00:01:20:30", "name": "eno1"}, - {"macAddress": "02:00:00:80:12:14", "name": "eth0"}, - {"macAddress": "02:00:00:80:12:15", "name": "eth1"}, + "name": "bond99", + "state": "up", + "type": "bond", + }, + }, + "routes": map[string]interface{}{ + "config": []map[string]interface{}{ + { + "destination": "0.0.0.0/0", + "next-hop-address": "192.0.2.254", + "next-hop-interface": "eno1", + "table": "", }, }, }, }, + "interfaces": []map[string]interface{}{ + {"macAddress": "00:00:00:01:20:30", "name": "eno1"}, + {"macAddress": "02:00:00:80:12:14", "name": "eth0"}, + {"macAddress": "02:00:00:80:12:15", "name": "eth1"}, + }, }, - } + }, + }, +} + +var _ = Describe("RenderTemplateForK8sCR", func() { + var ( + clusterInstanceObj map[string]interface{} + expectedRenderedYaml string + ) + + BeforeEach(func() { + data, err := yaml.Marshal(testClusterInstanceData) + Expect(err).ToNot(HaveOccurred()) + + // New var to store cluster data + clusterData := make(map[string]any) + Expect(yaml.Unmarshal(data, &clusterData)).To(Succeed()) + clusterInstanceObj = map[string]interface{}{"Cluster": clusterData} expectedRenderedYaml = ` apiVersion: siteconfig.open-cluster-management.io/v1alpha1 @@ -984,29 +990,28 @@ metadata: spec: additionalNTPSources: - NTP.server1 - - 10.16.231.22 + - 1.1.1.1 apiVIPs: - - 10.0.0.1 - - 10.0.0.2 + - 192.0.2.2 + - 192.0.2.3 baseDomain: example.com caBundleRef: name: my-bundle-ref clusterImageSetNameRef: "4.16" extraLabels: ManagedCluster: - common: "true" - group-du-sno: test - sites: site-sno-du-1 + cluster-version: v4.16 + clustertemplate-a-policy: v1 clusterName: site-sno-du-1 clusterNetwork: - - cidr: 10.128.0.0/14 + - cidr: 203.0.113.0/24 hostPrefix: 23 clusterType: SNO cpuPartitioningMode: AllNodes diskEncryption: tang: - thumbprint: "1234567890" - url: http://10.0.0.1:7500 + url: http://198.51.100.1:7500 type: nbde extraManifestsRefs: - name: foobar1 @@ -1016,11 +1021,11 @@ spec: installConfigOverrides: '{"capabilities":{"baselineCapabilitySet": "None", "additionalEnabledCapabilities": [ "marketplace", "NodeTuning" ] }}' machineNetwork: - - cidr: 10.16.231.0/24 + - cidr: 192.0.2.0/24 networkType: OVNKubernetes nodes: - automatedCleaningMode: disabled - bmcAddress: idrac-virtualmedia+https://10.16.231.87/redfish/v1/Systems/System.Embedded.1 + bmcAddress: idrac-virtualmedia+https://203.0.113.5/redfish/v1/Systems/System.Embedded.1 bmcCredentialsName: name: node1-bmc-secret bootMACAddress: "00:00:00:01:20:30" @@ -1036,33 +1041,33 @@ spec: dns-resolver: config: server: - - 10.19.42.41 + - 192.0.2.22 interfaces: - ipv4: address: - - ip: 10.16.231.3 + - ip: 192.0.2.10 prefix-length: 24 - - ip: 10.16.231.28 + - ip: 192.0.2.11 prefix-length: 24 - - ip: 10.16.231.31 + - ip: 192.0.2.12 prefix-length: 24 dhcp: false enabled: true ipv6: address: - - ip: 2620:52:0:10e7:e42:a1ff:fe8a:601 - prefix-length: 64 - - ip: 2620:52:0:10e7:e42:a1ff:fe8a:602 - prefix-length: 64 - - ip: 2620:52:0:10e7:e42:a1ff:fe8a:603 - prefix-length: 64 + - ip: 2001:db8:0:1::42 + prefix-length: 32 + - ip: 2001:db8:0:1::43 + prefix-length: 32 + - ip: 2001:db8:0:1::44 + prefix-length: 32 dhcp: false enabled: true name: eno1 type: ethernet - ipv6: address: - - ip: 2620:52:0:1302::100 + - ip: 2001:db8:abcd:1234::1 enabled: true link-aggregation: mode: balance-rr @@ -1071,14 +1076,14 @@ spec: slaves: - eth0 - eth1 - prefix-length: 64 + prefix-length: 32 name: bond99 state: up type: bond routes: config: - destination: 0.0.0.0/0 - next-hop-address: 10.16.231.254 + next-hop-address: 192.0.2.254 next-hop-interface: eno1 table: "" interfaces: @@ -1097,7 +1102,7 @@ spec: pullSecretRef: name: pullSecretName serviceNetwork: - - cidr: 172.30.0.0/16 + - cidr: 233.252.0.0/24 sshPublicKey: ssh-rsa templateRefs: - name: aci-cluster-crs-v1 @@ -1154,8 +1159,8 @@ spec: It("Return error if a required field is not provided", func() { // Remove the required field hostName - node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]map[string]any)[0] - delete(node1, "hostName") + node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]any)[0] + delete(node1.(map[string]any), "hostName") _, err := RenderTemplateForK8sCR( ClusterInstanceTemplateName, ClusterInstanceTemplatePath, clusterInstanceObj) @@ -1165,9 +1170,9 @@ spec: It("Return error if expected array field is not an array", func() { // Change the nodes.nodeNetwork.interfaces to a map - node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]map[string]any)[0] - delete(node1["nodeNetwork"].(map[string]any), "interfaces") - node1["nodeNetwork"].(map[string]any)["interfaces"] = map[string]any{"macAddress": "00:00:00:01:20:30", "name": "eno1"} + node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]any)[0] + delete(node1.(map[string]any)["nodeNetwork"].(map[string]any), "interfaces") + node1.(map[string]any)["nodeNetwork"].(map[string]any)["interfaces"] = map[string]any{"macAddress": "00:00:00:01:20:30", "name": "eno1"} _, err := RenderTemplateForK8sCR( ClusterInstanceTemplateName, ClusterInstanceTemplatePath, clusterInstanceObj) @@ -1177,9 +1182,9 @@ spec: It("Return error if expected map field is not a map", func() { // Change the nodes.nodeNetwork to string - node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]map[string]any)[0] - delete(node1, "nodeNetwork") - node1["nodeNetwork"] = "string" + node1 := clusterInstanceObj["Cluster"].(map[string]any)["nodes"].([]any)[0] + delete(node1.(map[string]any), "nodeNetwork") + node1.(map[string]any)["nodeNetwork"] = "string" _, err := RenderTemplateForK8sCR( ClusterInstanceTemplateName, ClusterInstanceTemplatePath, clusterInstanceObj) @@ -1188,6 +1193,124 @@ spec: }) }) +var _ = Describe("FindClusterInstanceImmutableFieldUpdates", func() { + var ( + oldClusterInstance *unstructured.Unstructured + newClusterInstance *unstructured.Unstructured + ) + + BeforeEach(func() { + // Initialize the old and new ClusterInstances + data, err := yaml.Marshal(testClusterInstanceData) + Expect(err).ToNot(HaveOccurred()) + + oldSpec := make(map[string]any) + newSpec := make(map[string]any) + Expect(yaml.Unmarshal(data, &oldSpec)).To(Succeed()) + Expect(yaml.Unmarshal(data, &newSpec)).To(Succeed()) + + oldClusterInstance = &unstructured.Unstructured{ + Object: map[string]any{"spec": oldSpec}, + } + + newClusterInstance = &unstructured.Unstructured{ + Object: map[string]any{"spec": newSpec}, + } + }) + + It("should return no updates when specs are identical", func() { + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(BeEmpty()) + Expect(scalingNodes).To(BeEmpty()) + }) + + It("should detect changes in immutable cluster-level fields", func() { + // Change an immutable field at the cluster-level + spec := newClusterInstance.Object["spec"].(map[string]any) + spec["baseDomain"] = "newdomain.example.com" + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(ContainElement("baseDomain")) + Expect(scalingNodes).To(BeEmpty()) + }) + + It("should not flag changes in allowed cluster-level fields", func() { + // Add an allowed extra label + spec := newClusterInstance.Object["spec"].(map[string]any) + fmt.Println("extraLabels ", spec["extraLabels"]) + labels := spec["extraLabels"].(map[string]any)["ManagedCluster"].(map[string]any) + labels["newLabelKey"] = "newLabelValue" + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(BeEmpty()) + Expect(scalingNodes).To(BeEmpty()) + }) + + It("should detect changes in disallowed node-level fields", func() { + // Change an immutable field in the node-level spec + spec := newClusterInstance.Object["spec"].(map[string]any) + node0 := spec["nodes"].([]any)[0].(map[string]any) + node0Network := node0["nodeNetwork"].(map[string]any)["config"].(map[string]any)["dns-resolver"].(map[string]any) + node0Network["config"].(map[string]any)["server"].([]any)[0] = "10.19.42.42" + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(ContainElement( + "nodes.0.nodeNetwork.config.dns-resolver.config.server.0")) + Expect(scalingNodes).To(BeEmpty()) + }) + + It("should detect addition of a new node", func() { + // Add a new node + spec := newClusterInstance.Object["spec"].(map[string]any) + nodes := spec["nodes"].([]any) + nodes = append(nodes, map[string]any{"hostName": "worker2"}) + spec["nodes"] = nodes + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(BeEmpty()) + Expect(scalingNodes).To(ContainElement("nodes.1")) + }) + + It("should detect deletion of a node", func() { + // Remove the node + spec := newClusterInstance.Object["spec"].(map[string]any) + spec["nodes"] = []any{} + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(BeEmpty()) + Expect(scalingNodes).To(ContainElement("nodes.0")) + }) + + It("should not flag changes in allowed node-level fields", func() { + // Add an allowed extra annotation at the node level + spec := newClusterInstance.Object["spec"].(map[string]any) + nodes := spec["nodes"].([]any) + nodes[0].(map[string]any)["extraAnnotations"] = map[string]map[string]string{ + "BareMetalHost": { + "newAnnotationKey": "newAnnotationValue", + }, + } + + updatedFields, scalingNodes, err := FindClusterInstanceImmutableFieldUpdates( + oldClusterInstance, newClusterInstance) + Expect(err).To(BeNil()) + Expect(updatedFields).To(BeEmpty()) + Expect(scalingNodes).To(BeEmpty()) + }) +}) + var _ = Describe("DeepMergeMaps and DeepMergeMapsSlices", func() { var ( dst map[string]interface{} diff --git a/vendor/github.com/r3labs/diff/v3/.gitignore b/vendor/github.com/r3labs/diff/v3/.gitignore new file mode 100644 index 00000000..e04e61e4 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +.idea/ + +patchflags_string.go diff --git a/vendor/github.com/r3labs/diff/v3/CONTRIBUTING.md b/vendor/github.com/r3labs/diff/v3/CONTRIBUTING.md new file mode 100644 index 00000000..3c00b27c --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing guidelines + +Looking to contribute something to this project? Here's how you can help: + +Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. + +We also have a [code of conduct](https://ernest.io/conduct). + +## Using the issue tracker + +The issue tracker is the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests) and [submitting pull requests](#pull-requests), but please respect the following restrictions: + +* Please **do not** use the issue tracker for personal support requests. + +* Please **do not** derail issues. Keep the discussion on topic and + respect the opinions of others. + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been + reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the + latest `master` or `develop` branch in the repository. + +3. **Isolate the problem** — create a reduced test case and a live example. + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? Which environment experience the problem? What would you expect to be the outcome? All these +details will help people to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic +help. They should remain focused in scope and avoid containing unrelated +commits. + +[**Please ask first**](https://ernest.io/community) before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). diff --git a/vendor/github.com/r3labs/diff/v3/LICENSE b/vendor/github.com/r3labs/diff/v3/LICENSE new file mode 100644 index 00000000..a612ad98 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/r3labs/diff/v3/Makefile b/vendor/github.com/r3labs/diff/v3/Makefile new file mode 100644 index 00000000..f119f8c1 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/Makefile @@ -0,0 +1,11 @@ +install: + go install -v ${LDFLAGS} + +deps: + go get github.com/stretchr/testify + +test: + @go test -v -cover ./... + +cover: + @go test -coverprofile cover.out diff --git a/vendor/github.com/r3labs/diff/v3/README.md b/vendor/github.com/r3labs/diff/v3/README.md new file mode 100644 index 00000000..97aee65e --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/README.md @@ -0,0 +1,327 @@ +# Diff [![PkgGoDev](https://pkg.go.dev/badge/github.com/r3labs/diff)](https://pkg.go.dev/github.com/r3labs/diff) [![Go Report Card](https://goreportcard.com/badge/github.com/r3labs/diff)](https://goreportcard.com/report/github.com/r3labs/diff) [![Build Status](https://travis-ci.com/r3labs/diff.svg?branch=master)](https://travis-ci.com/r3labs/diff) + +A library for diffing golang structures and values. + +Utilizing field tags and reflection, it is able to compare two structures of the same type and create a changelog of all modified values. The produced changelog can easily be serialized to json. + +NOTE: All active development now takes place on the v3 branch. + +## Installation + +For version 3: +``` +go get github.com/r3labs/diff/v3 +``` + +## Changelog Format + +When diffing two structures using `Diff`, a changelog will be produced. Any detected changes will populate the changelog array with a Change type: + +```go +type Change struct { + Type string // The type of change detected; can be one of create, update or delete + Path []string // The path of the detected change; will contain any field name or array index that was part of the traversal + From interface{} // The original value that was present in the "from" structure + To interface{} // The new value that was detected as a change in the "to" structure +} +``` + +Given the example below, we are diffing two slices where the third element has been removed: + +```go +from := []int{1, 2, 3, 4} +to := []int{1, 2, 4} + +changelog, _ := diff.Diff(from, to) +``` + +The resultant changelog should contain one change: + +```go +Change{ + Type: "delete", + Path: ["2"], + From: 3, + To: nil, +} +``` + +## Supported Types + +A diffable value can be/contain any of the following types: + + +| Type | Supported | +| ------------ | --------- | +| struct | ✔ | +| slice | ✔ | +| string | ✔ | +| int | ✔ | +| bool | ✔ | +| map | ✔ | +| pointer | ✔ | +| custom types | ✔ | + + +Please see the docs for more supported types, options and features. + +### Tags + +In order for struct fields to be compared, they must be tagged with a given name. All tag values are prefixed with `diff`. i.e. `diff:"items"`. + +| Tag | Usage | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-` | Excludes a value from being diffed | +| `identifier` | If you need to compare arrays by a matching identifier and not based on order, you can specify the `identifier` tag. If an identifiable element is found in both the from and to structures, they will be directly compared. i.e. `diff:"name, identifier"` | +| `immutable` | Will omit this struct field from diffing. When using `diff.StructValues()` these values will be added to the returned changelog. It's use case is for when we have nothing to compare a struct to and want to show all of its relevant values. | +| `nocreate` | The default patch action is to allocate instances in the target strut, map or slice should they not exist. Adding this flag will tell patch to skip elements that it would otherwise need to allocate. This is separate from immutable, which is also honored while patching. | +| `omitunequal` | Patching is a 'best effort' operation, and will by default attempt to update the 'correct' member of the target even if the underlying value has already changed to something other than the value in the change log 'from'. This tag will selectively ignore values that are not a 100% match. | + +## Usage + +### Basic Example + +Diffing a basic set of values can be accomplished using the diff functions. Any items that specify a "diff" tag using a name will be compared. + +```go +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `diff:"id"` + Items []int `diff:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + changelog, err := diff.Diff(a, b) + ... +} +``` + +In this example, the output generated in the changelog will indicate that the third element with a value of '3' was removed from items. +When marshalling the changelog to json, the output will look like: + +```json +[ + { + "type": "delete", + "path": ["items", "2"], + "from": 3, + "to": null + } +] +``` + +### Options and Configuration + +Options can be set on the differ at call time which effect how diff acts when building the change log. +```go +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `diff:"id"` + Items []int `diff:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + changelog, err := diff.Diff(a, b, diff.DisableStructValues(), diff.AllowTypeMismatch(true)) + ... +} +``` + +You can also create a new instance of a differ that allows options to be set. + +```go +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `diff:"id"` + Items []int `diff:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + d, err := diff.NewDiffer(diff.SliceOrdering(true)) + if err != nil { + panic(err) + } + + changelog, err := d.Diff(a, b) + ... +} +``` + +Supported options are: + +`SliceOrdering` ensures that the ordering of items in a slice is taken into account + +`DiscardComplexOrigin` is a directive to diff to omit additional origin information about structs. This alters the behavior of patch and can lead to some pitfalls and non-intuitive behavior if used. On the other hand, it can significantly reduce the memory footprint of large complex diffs. + +`AllowTypeMismatch` is a global directive to either allow (true) or not to allow (false) patch apply the changes if 'from' is not equal. This is effectively a global version of the omitunequal tag. + +`Filter` provides a callback that allows you to determine which fields the differ descends into + +`DisableStructValues` disables populating a separate change for each item in a struct, where the struct is being compared to a nil Value. + +`TagName` sets the tag name to use when getting field names and options. + +### Patch and merge support +Diff additionally supports merge and patch. Similar in concept to text patching / merging the Patch function, given +a change log and a target instance will make a _best effort_ to apply the changes in the change log to the variable +pointed to. The intention is that the target pointer is of the same type however, that doesn't necessarily have to be +true. For example, two slices of differing structs may be similar enough to apply changes to in a polymorphic way, and +patch will certainly try. + +The patch function doesn't actually fail, and even if there are errors, it may succeed sufficiently for the task at hand. +To accommodate this patch keeps track of each change log option it attempts to apply and reports the details of what +happened for further scrutiny. + +```go +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `diff:"id"` + Items []int `diff:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + c := Order{} + changelog, err := diff.Diff(a, b) + + patchlog := diff.Patch(changelog, &c) + //Note the lack of an error. Patch is best effort and uses flags to indicate actions taken + //and keeps any errors encountered along the way for review + fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount()) + ... +} +``` + +Instances of differ with options set can also be used when patching. + +```go +package main + +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `json:"id"` + Items []int `json:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + d, _ := diff.NewDiffer(diff.TagName("json")) + + changelog, _ := d.Diff(a, b) + + d.Patch(changelog, &a) + // reflect.DeepEqual(a, b) == true +} + +``` + +As a convenience, there is a Merge function that allows one to take three interfaces and perform all the tasks at the same +time. + +```go +import "github.com/r3labs/diff/v3" + +type Order struct { + ID string `diff:"id"` + Items []int `diff:"items"` +} + +func main() { + a := Order{ + ID: "1234", + Items: []int{1, 2, 3, 4}, + } + + b := Order{ + ID: "1234", + Items: []int{1, 2, 4}, + } + + c := Order{} + patchlog, err := diff.Merge(a, b, &c) + if err != nil { + fmt.Printf("Error encountered while diffing a & b") + } + fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount()) + ... +} +``` +## Running Tests + +``` +make test +``` + +## Contributing + +Please read through our +[contributing guidelines](CONTRIBUTING.md). +Included are directions for opening issues, coding standards, and notes on +development. + +Moreover, if your pull request contains patches or features, you must include +relevant unit tests. + +## Versioning + +For transparency into our release cycle and in striving to maintain backward +compatibility, this project is maintained under [the Semantic Versioning guidelines](http://semver.org/). + +## Copyright and License + +Code and documentation copyright since 2015 r3labs.io authors. + +Code released under +[the Mozilla Public License Version 2.0](LICENSE). diff --git a/vendor/github.com/r3labs/diff/v3/change_value.go b/vendor/github.com/r3labs/diff/v3/change_value.go new file mode 100644 index 00000000..b0f3a738 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/change_value.go @@ -0,0 +1,209 @@ +package diff + +import ( + "fmt" + "reflect" +) + +//ChangeValue is a specialized struct for monitoring patching +type ChangeValue struct { + parent *reflect.Value + target *reflect.Value + flags PatchFlags + change *Change + err error + pos int + index int + key reflect.Value +} + +//swap swaps out the target as we move down the path. Note that a nil +// check is foregone here due to the fact we control usage. +func (c *ChangeValue) swap(newTarget *reflect.Value) { + if newTarget.IsValid() { + c.ClearFlag(FlagInvalidTarget) + c.parent = c.target + c.target = newTarget + c.pos++ + } +} + +// Sets a flag on the node and saves the change +func (c *ChangeValue) SetFlag(flag PatchFlags) { + if c != nil { + c.flags = c.flags | flag + } +} + +//ClearFlag removes just a single flag +func (c *ChangeValue) ClearFlag(flag PatchFlags) { + if c != nil { + c.flags = c.flags &^ flag + } +} + +//HasFlag indicates if a flag is set on the node. returns false if node is bad +func (c *ChangeValue) HasFlag(flag PatchFlags) bool { + return (c.flags & flag) != 0 +} + +//IsValid echo for is valid +func (c *ChangeValue) IsValid() bool { + if c != nil { + return c.target.IsValid() || !c.HasFlag(FlagInvalidTarget) + } + return false +} + +//ParentKind - helps keep us nil safe +func (c ChangeValue) ParentKind() reflect.Kind { + if c.parent != nil { + return c.parent.Kind() + } + return reflect.Invalid +} + +//ParentLen is a nil safe parent length check +func (c ChangeValue) ParentLen() (ret int) { + if c.parent != nil && + (c.parent.Kind() == reflect.Slice || + c.parent.Kind() == reflect.Map) { + ret = c.parent.Len() + } + return +} + +//ParentSet - nil safe parent set +func (c *ChangeValue) ParentSet(value reflect.Value, convertCompatibleTypes bool) { + if c != nil && c.parent != nil { + defer func() { + if r := recover(); r != nil { + c.SetFlag(FlagParentSetFailed) + } + }() + + if convertCompatibleTypes { + if !value.Type().ConvertibleTo(c.parent.Type()) { + c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.parent.Type().String())) + c.SetFlag(FlagParentSetFailed) + return + } + c.parent.Set(value.Convert(c.parent.Type())) + } else { + c.parent.Set(value) + } + c.SetFlag(FlagParentSetApplied) + } +} + +//Len echo for len +func (c ChangeValue) Len() int { + return c.target.Len() +} + +//Set echos reflect set +func (c *ChangeValue) Set(value reflect.Value, convertCompatibleTypes bool) { + if c == nil { + return + } + + defer func() { + if r := recover(); r != nil { + switch e := r.(type) { + case string: + c.AddError(NewError(e)) + case *reflect.ValueError: + c.AddError(NewError(e.Error())) + } + + c.SetFlag(FlagFailed) + } + }() + + if c.HasFlag(OptionImmutable) { + c.SetFlag(FlagIgnored) + return + } + + if convertCompatibleTypes { + if c.target.Kind() == reflect.Ptr && value.Kind() != reflect.Ptr { + if !value.IsValid() { + c.target.Set(reflect.Zero(c.target.Type())) + c.SetFlag(FlagApplied) + return + } else if !value.Type().ConvertibleTo(c.target.Elem().Type()) { + c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.target.Type().String())) + c.SetFlag(FlagFailed) + return + } + + tv := reflect.New(c.target.Elem().Type()) + tv.Elem().Set(value.Convert(c.target.Elem().Type())) + c.target.Set(tv) + } else { + if !value.Type().ConvertibleTo(c.target.Type()) { + c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.target.Type().String())) + c.SetFlag(FlagFailed) + return + } + + c.target.Set(value.Convert(c.target.Type())) + } + } else { + if value.IsValid() { + if c.target.Kind() == reflect.Ptr && value.Kind() != reflect.Ptr { + tv := reflect.New(value.Type()) + tv.Elem().Set(value) + c.target.Set(tv) + } else { + c.target.Set(value) + } + } else if c.target.Kind() == reflect.Ptr { + c.target.Set(reflect.Zero(c.target.Type())) + } else if !c.target.IsZero() { + t := c.target.Elem() + t.Set(reflect.Zero(t.Type())) + } + } + c.SetFlag(FlagApplied) +} + +//Index echo for index +func (c ChangeValue) Index(i int) reflect.Value { + return c.target.Index(i) +} + +//ParentIndex - get us the parent version, nil safe +func (c ChangeValue) ParentIndex(i int) (ret reflect.Value) { + if c.parent != nil { + ret = c.parent.Index(i) + } + return +} + +//Instance a new element of type for target. Taking the +//copy of the complex origin avoids the 'lack of data' issue +//present when allocating complex structs with slices and +//arrays +func (c ChangeValue) NewElement() reflect.Value { + ret := c.change.parent + if ret != nil { + return reflect.ValueOf(ret) + } + return reflect.New(c.target.Type().Elem()).Elem() +} + +//NewArrayElement gives us a dynamically typed new element +func (c ChangeValue) NewArrayElement() reflect.Value { + c.target.Set(reflect.Append(*c.target, c.NewElement())) + c.SetFlag(FlagCreated) + return c.Index(c.Len() - 1) +} + +//AddError appends errors to this change value +func (c *ChangeValue) AddError(err error) *ChangeValue { + if c != nil { + c.err = err + } + return c +} diff --git a/vendor/github.com/r3labs/diff/v3/comparative.go b/vendor/github.com/r3labs/diff/v3/comparative.go new file mode 100644 index 00000000..f92ff6bd --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/comparative.go @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +// Comparative ... +type Comparative struct { + A, B *reflect.Value +} + +// ComparativeList : stores indexed comparative +type ComparativeList struct { + m map[interface{}]*Comparative + keys []interface{} +} + +// NewComparativeList : returns a new comparative list +func NewComparativeList() *ComparativeList { + return &ComparativeList{ + m: make(map[interface{}]*Comparative), + keys: make([]interface{}, 0), + } +} + +func (cl *ComparativeList) addA(k interface{}, v *reflect.Value) { + if (*cl).m[k] == nil { + (*cl).m[k] = &Comparative{} + (*cl).keys = append((*cl).keys, k) + } + (*cl).m[k].A = v +} + +func (cl *ComparativeList) addB(k interface{}, v *reflect.Value) { + if (*cl).m[k] == nil { + (*cl).m[k] = &Comparative{} + (*cl).keys = append((*cl).keys, k) + } + (*cl).m[k].B = v +} diff --git a/vendor/github.com/r3labs/diff/v3/diff.go b/vendor/github.com/r3labs/diff/v3/diff.go new file mode 100644 index 00000000..3c2ba176 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff.go @@ -0,0 +1,417 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/vmihailenco/msgpack/v5" +) + +const ( + // CREATE represents when an element has been added + CREATE = "create" + // UPDATE represents when an element has been updated + UPDATE = "update" + // DELETE represents when an element has been removed + DELETE = "delete" +) + +// DiffType represents an enum with all the supported diff types +type DiffType uint8 + +const ( + UNSUPPORTED DiffType = iota + STRUCT + SLICE + ARRAY + STRING + BOOL + INT + UINT + FLOAT + MAP + PTR + INTERFACE +) + +func (t DiffType) String() string { + switch t { + case STRUCT: + return "STRUCT" + case SLICE: + return "SLICE" + case ARRAY: + return "ARRAY" + case STRING: + return "STRING" + case BOOL: + return "BOOL" + case INT: + return "INT" + case UINT: + return "UINT" + case FLOAT: + return "FLOAT" + case MAP: + return "MAP" + case PTR: + return "PTR" + case INTERFACE: + return "INTERFACE" + default: + return "UNSUPPORTED" + } +} + +// DiffFunc represents the built-in diff functions +type DiffFunc func([]string, reflect.Value, reflect.Value, interface{}) error + +// Differ a configurable diff instance +type Differ struct { + TagName string + SliceOrdering bool + DisableStructValues bool + customValueDiffers []ValueDiffer + cl Changelog + AllowTypeMismatch bool + DiscardParent bool + StructMapKeys bool + FlattenEmbeddedStructs bool + ConvertCompatibleTypes bool + Filter FilterFunc +} + +// Changelog stores a list of changed items +type Changelog []Change + +// Change stores information about a changed item +type Change struct { + Type string `json:"type"` + Path []string `json:"path"` + From interface{} `json:"from"` + To interface{} `json:"to"` + parent interface{} `json:"parent"` +} + +// ValueDiffer is an interface for custom differs +type ValueDiffer interface { + Match(a, b reflect.Value) bool + Diff(dt DiffType, df DiffFunc, cl *Changelog, path []string, a, b reflect.Value, parent interface{}) error + InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) +} + +// Changed returns true if both values differ +func Changed(a, b interface{}) bool { + cl, _ := Diff(a, b) + return len(cl) > 0 +} + +// Diff returns a changelog of all mutated values from both +func Diff(a, b interface{}, opts ...func(d *Differ) error) (Changelog, error) { + d, err := NewDiffer(opts...) + if err != nil { + return nil, err + } + return d.Diff(a, b) +} + +// NewDiffer creates a new configurable diffing object +func NewDiffer(opts ...func(d *Differ) error) (*Differ, error) { + d := Differ{ + TagName: "diff", + DiscardParent: false, + } + + for _, opt := range opts { + err := opt(&d) + if err != nil { + return nil, err + } + } + + return &d, nil +} + +// FilterFunc is a function that determines whether to descend into a struct field. +// parent is the struct being examined and field is a field on that struct. path +// is the path to the field from the root of the diff. +type FilterFunc func(path []string, parent reflect.Type, field reflect.StructField) bool + +// StructValues gets all values from a struct +// values are stored as "created" or "deleted" entries in the changelog, +// depending on the change type specified +func StructValues(t string, path []string, s interface{}) (Changelog, error) { + d := Differ{ + TagName: "diff", + DiscardParent: false, + } + + v := reflect.ValueOf(s) + + return d.cl, d.structValues(t, path, v) +} + +// FilterOut filter out the changes based on path. Paths may contain valid regexp to match items +func (cl *Changelog) FilterOut(path []string) Changelog { + var ncl Changelog + + for _, c := range *cl { + if !pathmatch(path, c.Path) { + ncl = append(ncl, c) + } + } + + return ncl +} + +// Filter filter changes based on path. Paths may contain valid regexp to match items +func (cl *Changelog) Filter(path []string) Changelog { + var ncl Changelog + + for _, c := range *cl { + if pathmatch(path, c.Path) { + ncl = append(ncl, c) + } + } + + return ncl +} + +func (d *Differ) getDiffType(a, b reflect.Value) (DiffType, DiffFunc) { + switch { + case are(a, b, reflect.Struct, reflect.Invalid): + return STRUCT, d.diffStruct + case are(a, b, reflect.Slice, reflect.Invalid): + return SLICE, d.diffSlice + case are(a, b, reflect.Array, reflect.Invalid): + return ARRAY, d.diffSlice + case are(a, b, reflect.String, reflect.Invalid): + return STRING, d.diffString + case are(a, b, reflect.Bool, reflect.Invalid): + return BOOL, d.diffBool + case are(a, b, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Invalid): + return INT, d.diffInt + case are(a, b, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Invalid): + return UINT, d.diffUint + case are(a, b, reflect.Float32, reflect.Float64, reflect.Invalid): + return FLOAT, d.diffFloat + case are(a, b, reflect.Map, reflect.Invalid): + return MAP, d.diffMap + case are(a, b, reflect.Ptr, reflect.Invalid): + return PTR, d.diffPtr + case are(a, b, reflect.Interface, reflect.Invalid): + return INTERFACE, d.diffInterface + default: + return UNSUPPORTED, nil + } +} + +// Diff returns a changelog of all mutated values from both +func (d *Differ) Diff(a, b interface{}) (Changelog, error) { + // reset the state of the diff + d.cl = Changelog{} + + return d.cl, d.diff([]string{}, reflect.ValueOf(a), reflect.ValueOf(b), nil) +} + +func (d *Differ) diff(path []string, a, b reflect.Value, parent interface{}) error { + + //look and see if we need to discard the parent + if parent != nil { + if d.DiscardParent || reflect.TypeOf(parent).Kind() != reflect.Struct { + parent = nil + } + } + + // check if types match or are + if invalid(a, b) { + if d.AllowTypeMismatch { + d.cl.Add(UPDATE, path, a.Interface(), b.Interface()) + return nil + } + return ErrTypeMismatch + } + + // get the diff type and the corresponding built-int diff function to handle this type + diffType, diffFunc := d.getDiffType(a, b) + + // first go through custom diff functions + if len(d.customValueDiffers) > 0 { + for _, vd := range d.customValueDiffers { + if vd.Match(a, b) { + err := vd.Diff(diffType, diffFunc, &d.cl, path, a, b, parent) + if err != nil { + return err + } + return nil + } + } + } + + // then built-in diff functions + if diffType == UNSUPPORTED { + return errors.New("unsupported type: " + a.Kind().String()) + } + + return diffFunc(path, a, b, parent) +} + +func (cl *Changelog) Add(t string, path []string, ftco ...interface{}) { + change := Change{ + Type: t, + Path: path, + From: ftco[0], + To: ftco[1], + } + if len(ftco) > 2 { + change.parent = ftco[2] + } + (*cl) = append((*cl), change) +} + +func tagName(tag string, f reflect.StructField) string { + t := f.Tag.Get(tag) + + parts := strings.Split(t, ",") + if len(parts) < 1 { + return "-" + } + + return parts[0] +} + +func identifier(tag string, v reflect.Value) interface{} { + if v.Kind() != reflect.Struct { + return nil + } + + for i := 0; i < v.NumField(); i++ { + if hasTagOption(tag, v.Type().Field(i), "identifier") { + return v.Field(i).Interface() + } + } + + return nil +} + +func hasTagOption(tag string, f reflect.StructField, opt string) bool { + parts := strings.Split(f.Tag.Get(tag), ",") + if len(parts) < 2 { + return false + } + + for _, option := range parts[1:] { + if option == opt { + return true + } + } + + return false +} + +func swapChange(t string, c Change) Change { + nc := Change{ + Type: t, + Path: c.Path, + } + + switch t { + case CREATE: + nc.To = c.To + case DELETE: + nc.From = c.To + } + + return nc +} + +func idComplex(v interface{}) string { + switch v := v.(type) { + case string: + return v + case int: + return strconv.Itoa(v) + default: + b, err := msgpack.Marshal(v) + if err != nil { + panic(err) + } + return string(b) + } + +} +func idstring(v interface{}) string { + switch v := v.(type) { + case string: + return v + case int: + return strconv.Itoa(v) + default: + return fmt.Sprint(v) + } +} + +func invalid(a, b reflect.Value) bool { + if a.Kind() == b.Kind() { + return false + } + + if a.Kind() == reflect.Invalid { + return false + } + if b.Kind() == reflect.Invalid { + return false + } + + return true +} + +func are(a, b reflect.Value, kinds ...reflect.Kind) bool { + var amatch, bmatch bool + + for _, k := range kinds { + if a.Kind() == k { + amatch = true + } + if b.Kind() == k { + bmatch = true + } + } + + return amatch && bmatch +} + +func AreType(a, b reflect.Value, types ...reflect.Type) bool { + var amatch, bmatch bool + + for _, t := range types { + if a.Kind() != reflect.Invalid { + if a.Type() == t { + amatch = true + } + } + if b.Kind() != reflect.Invalid { + if b.Type() == t { + bmatch = true + } + } + } + + return amatch && bmatch +} + +func copyAppend(src []string, elems ...string) []string { + dst := make([]string, len(src)+len(elems)) + copy(dst, src) + for i := len(src); i < len(src)+len(elems); i++ { + dst[i] = elems[i-len(src)] + } + return dst +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_bool.go b/vendor/github.com/r3labs/diff/v3/diff_bool.go new file mode 100644 index 00000000..0e305033 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_bool.go @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import "reflect" + +func (d *Differ) diffBool(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.Bool() != b.Bool() { + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent) + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_comparative.go b/vendor/github.com/r3labs/diff/v3/diff_comparative.go new file mode 100644 index 00000000..7359d17f --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_comparative.go @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +func (d *Differ) diffComparative(path []string, c *ComparativeList, parent interface{}) error { + for _, k := range c.keys { + id := idstring(k) + if d.StructMapKeys { + id = idComplex(k) + } + + fpath := copyAppend(path, id) + nv := reflect.ValueOf(nil) + + if c.m[k].A == nil { + c.m[k].A = &nv + } + + if c.m[k].B == nil { + c.m[k].B = &nv + } + + err := d.diff(fpath, *c.m[k].A, *c.m[k].B, parent) + if err != nil { + return err + } + } + + return nil +} + +func (d *Differ) comparative(a, b reflect.Value) bool { + if a.Len() > 0 { + ae := a.Index(0) + ak := getFinalValue(ae) + + if ak.Kind() == reflect.Struct { + if identifier(d.TagName, ak) != nil { + return true + } + } + } + + if b.Len() > 0 { + be := b.Index(0) + bk := getFinalValue(be) + + if bk.Kind() == reflect.Struct { + if identifier(d.TagName, bk) != nil { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_float.go b/vendor/github.com/r3labs/diff/v3/diff_float.go new file mode 100644 index 00000000..9494365e --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_float.go @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +func (d *Differ) diffFloat(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.Float() != b.Float() { + if a.CanInterface() { + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent) + } else { + d.cl.Add(UPDATE, path, a.Float(), b.Float(), parent) + } + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_int.go b/vendor/github.com/r3labs/diff/v3/diff_int.go new file mode 100644 index 00000000..3658bf77 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_int.go @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +func (d *Differ) diffInt(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.Int() != b.Int() { + if a.CanInterface() { + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent) + } else { + d.cl.Add(UPDATE, path, a.Int(), b.Int(), parent) + } + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_interface.go b/vendor/github.com/r3labs/diff/v3/diff_interface.go new file mode 100644 index 00000000..ef6cde87 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_interface.go @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import "reflect" + +func (d *Differ) diffInterface(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.IsNil() && b.IsNil() { + return nil + } + + if a.IsNil() { + d.cl.Add(UPDATE, path, nil, exportInterface(b), parent) + return nil + } + + if b.IsNil() { + d.cl.Add(UPDATE, path, exportInterface(a), nil, parent) + return nil + } + + return d.diff(path, a.Elem(), b.Elem(), parent) +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_map.go b/vendor/github.com/r3labs/diff/v3/diff_map.go new file mode 100644 index 00000000..675ff931 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_map.go @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "fmt" + "reflect" + + "github.com/vmihailenco/msgpack/v5" +) + +func (d *Differ) diffMap(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + return d.mapValues(CREATE, path, b) + } + + if b.Kind() == reflect.Invalid { + return d.mapValues(DELETE, path, a) + } + + c := NewComparativeList() + + for _, k := range a.MapKeys() { + ae := a.MapIndex(k) + c.addA(exportInterface(k), &ae) + } + + for _, k := range b.MapKeys() { + be := b.MapIndex(k) + c.addB(exportInterface(k), &be) + } + + return d.diffComparative(path, c, exportInterface(a)) +} + +func (d *Differ) mapValues(t string, path []string, a reflect.Value) error { + if t != CREATE && t != DELETE { + return ErrInvalidChangeType + } + + if a.Kind() == reflect.Ptr { + a = reflect.Indirect(a) + } + + if a.Kind() != reflect.Map { + return ErrTypeMismatch + } + + x := reflect.New(a.Type()).Elem() + + for _, k := range a.MapKeys() { + ae := a.MapIndex(k) + xe := x.MapIndex(k) + + var err error + if d.StructMapKeys { + //it's not enough to turn k to a string, we need to able to marshal a type when + //we apply it in patch so... we'll marshal it to JSON + var b []byte + if b, err = msgpack.Marshal(k.Interface()); err == nil { + err = d.diff(append(path, string(b)), xe, ae, a.Interface()) + } + } else { + err = d.diff(append(path, fmt.Sprint(k.Interface())), xe, ae, a.Interface()) + } + if err != nil { + return err + } + } + + for i := 0; i < len(d.cl); i++ { + // only swap changes on the relevant map + if pathmatch(path, d.cl[i].Path) { + d.cl[i] = swapChange(t, d.cl[i]) + } + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_pointer.go b/vendor/github.com/r3labs/diff/v3/diff_pointer.go new file mode 100644 index 00000000..7c9d8751 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_pointer.go @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" + "unsafe" +) + +var isExportFlag uintptr = (1 << 5) | (1 << 6) + +func (d *Differ) diffPtr(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() != b.Kind() { + if a.Kind() == reflect.Invalid { + if !b.IsNil() { + return d.diff(path, reflect.ValueOf(nil), reflect.Indirect(b), parent) + } + + d.cl.Add(CREATE, path, nil, exportInterface(b), parent) + return nil + } + + if b.Kind() == reflect.Invalid { + if !a.IsNil() { + return d.diff(path, reflect.Indirect(a), reflect.ValueOf(nil), parent) + } + + d.cl.Add(DELETE, path, exportInterface(a), nil, parent) + return nil + } + + return ErrTypeMismatch + } + + if a.IsNil() && b.IsNil() { + return nil + } + + if a.IsNil() { + d.cl.Add(UPDATE, path, nil, exportInterface(b), parent) + return nil + } + + if b.IsNil() { + d.cl.Add(UPDATE, path, exportInterface(a), nil, parent) + return nil + } + + return d.diff(path, reflect.Indirect(a), reflect.Indirect(b), parent) +} + +func exportInterface(v reflect.Value) interface{} { + if !v.CanInterface() { + flagTmp := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 2*unsafe.Sizeof(uintptr(0)))) + *flagTmp = (*flagTmp) & (^isExportFlag) + } + return v.Interface() +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_slice.go b/vendor/github.com/r3labs/diff/v3/diff_slice.go new file mode 100644 index 00000000..3fd281b9 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_slice.go @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +func (d *Differ) diffSlice(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if d.comparative(a, b) { + return d.diffSliceComparative(path, a, b) + } + + return d.diffSliceGeneric(path, a, b) +} + +func (d *Differ) diffSliceGeneric(path []string, a, b reflect.Value) error { + missing := NewComparativeList() + + slice := sliceTracker{} + for i := 0; i < a.Len(); i++ { + ae := a.Index(i) + + if (d.SliceOrdering && !hasAtSameIndex(b, ae, i)) || (!d.SliceOrdering && !slice.has(b, ae, d)) { + missing.addA(i, &ae) + } + } + + slice = sliceTracker{} + for i := 0; i < b.Len(); i++ { + be := b.Index(i) + + if (d.SliceOrdering && !hasAtSameIndex(a, be, i)) || (!d.SliceOrdering && !slice.has(a, be, d)) { + missing.addB(i, &be) + } + } + + // fallback to comparing based on order in slice if item is missing + if len(missing.keys) == 0 { + return nil + } + + return d.diffComparative(path, missing, exportInterface(a)) +} + +func (d *Differ) diffSliceComparative(path []string, a, b reflect.Value) error { + c := NewComparativeList() + + for i := 0; i < a.Len(); i++ { + ae := a.Index(i) + ak := getFinalValue(ae) + + id := identifier(d.TagName, ak) + if id != nil { + c.addA(id, &ae) + } + } + + for i := 0; i < b.Len(); i++ { + be := b.Index(i) + bk := getFinalValue(be) + + id := identifier(d.TagName, bk) + if id != nil { + c.addB(id, &be) + } + } + + return d.diffComparative(path, c, exportInterface(a)) +} + +// keeps track of elements that have already been matched, to stop duplicate matches from occurring +type sliceTracker []bool + +func (st *sliceTracker) has(s, v reflect.Value, d *Differ) bool { + if len(*st) != s.Len() { + (*st) = make([]bool, s.Len()) + } + + for i := 0; i < s.Len(); i++ { + // skip already matched elements + if (*st)[i] { + continue + } + + x := s.Index(i) + + var nd Differ + nd.Filter = d.Filter + nd.customValueDiffers = d.customValueDiffers + + err := nd.diff([]string{}, x, v, nil) + if err != nil { + continue + } + + if len(nd.cl) == 0 { + (*st)[i] = true + return true + } + } + + return false +} + +func getFinalValue(t reflect.Value) reflect.Value { + switch t.Kind() { + case reflect.Interface: + return getFinalValue(t.Elem()) + case reflect.Ptr: + return getFinalValue(reflect.Indirect(t)) + default: + return t + } +} + +func hasAtSameIndex(s, v reflect.Value, atIndex int) bool { + // check the element in the slice at atIndex to see if it matches Value, if it is a valid index into the slice + if atIndex < s.Len() { + x := s.Index(atIndex) + return reflect.DeepEqual(exportInterface(x), exportInterface(v)) + } + + return false +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_string.go b/vendor/github.com/r3labs/diff/v3/diff_string.go new file mode 100644 index 00000000..74182e2f --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_string.go @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import "reflect" + +func (d *Differ) diffString(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.String() != b.String() { + if a.CanInterface() { + // If a and/or b is of a type that is an alias for String, store that type in changelog + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent) + } else { + d.cl.Add(UPDATE, path, a.String(), b.String(), parent) + } + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_struct.go b/vendor/github.com/r3labs/diff/v3/diff_struct.go new file mode 100644 index 00000000..fb14c57c --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_struct.go @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" + "time" +) + +func (d *Differ) diffStruct(path []string, a, b reflect.Value, parent interface{}) error { + if AreType(a, b, reflect.TypeOf(time.Time{})) { + return d.diffTime(path, a, b) + } + + if a.Kind() == reflect.Invalid { + if d.DisableStructValues { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + return d.structValues(CREATE, path, b) + } + + if b.Kind() == reflect.Invalid { + if d.DisableStructValues { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + return d.structValues(DELETE, path, a) + } + + for i := 0; i < a.NumField(); i++ { + field := a.Type().Field(i) + tname := tagName(d.TagName, field) + + if tname == "-" || hasTagOption(d.TagName, field, "immutable") { + continue + } + + if tname == "" { + tname = field.Name + } + + af := a.Field(i) + bf := b.FieldByName(field.Name) + + fpath := path + if !(d.FlattenEmbeddedStructs && field.Anonymous) { + fpath = copyAppend(fpath, tname) + } + + if d.Filter != nil && !d.Filter(fpath, a.Type(), field) { + continue + } + + // skip private fields + if !a.CanInterface() { + continue + } + + err := d.diff(fpath, af, bf, exportInterface(a)) + if err != nil { + return err + } + } + + return nil +} + +func (d *Differ) structValues(t string, path []string, a reflect.Value) error { + var nd Differ + nd.Filter = d.Filter + nd.customValueDiffers = d.customValueDiffers + + if t != CREATE && t != DELETE { + return ErrInvalidChangeType + } + + if a.Kind() == reflect.Ptr { + a = reflect.Indirect(a) + } + + if a.Kind() != reflect.Struct { + return ErrTypeMismatch + } + + x := reflect.New(a.Type()).Elem() + + for i := 0; i < a.NumField(); i++ { + + field := a.Type().Field(i) + tname := tagName(d.TagName, field) + + if tname == "-" { + continue + } + + if tname == "" { + tname = field.Name + } + + af := a.Field(i) + xf := x.FieldByName(field.Name) + + fpath := copyAppend(path, tname) + + if nd.Filter != nil && !nd.Filter(fpath, a.Type(), field) { + continue + } + + err := nd.diff(fpath, xf, af, exportInterface(a)) + if err != nil { + return err + } + } + + for i := 0; i < len(nd.cl); i++ { + (d.cl) = append(d.cl, swapChange(t, nd.cl[i])) + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_time.go b/vendor/github.com/r3labs/diff/v3/diff_time.go new file mode 100644 index 00000000..4275e4ae --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_time.go @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" + "time" +) + +func (d *Differ) diffTime(path []string, a, b reflect.Value) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + // Marshal and unmarshal time type will lose accuracy. Using unix nano to compare time type. + au := exportInterface(a).(time.Time).UnixNano() + bu := exportInterface(b).(time.Time).UnixNano() + + if au != bu { + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b)) + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/diff_uint.go b/vendor/github.com/r3labs/diff/v3/diff_uint.go new file mode 100644 index 00000000..fbe133f1 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/diff_uint.go @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import ( + "reflect" +) + +func (d *Differ) diffUint(path []string, a, b reflect.Value, parent interface{}) error { + if a.Kind() == reflect.Invalid { + d.cl.Add(CREATE, path, nil, exportInterface(b)) + return nil + } + + if b.Kind() == reflect.Invalid { + d.cl.Add(DELETE, path, exportInterface(a), nil) + return nil + } + + if a.Kind() != b.Kind() { + return ErrTypeMismatch + } + + if a.Uint() != b.Uint() { + if a.CanInterface() { + d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent) + } else { + d.cl.Add(UPDATE, path, a.Uint(), b.Uint(), parent) + } + } + + return nil +} diff --git a/vendor/github.com/r3labs/diff/v3/error.go b/vendor/github.com/r3labs/diff/v3/error.go new file mode 100644 index 00000000..0acc13ff --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/error.go @@ -0,0 +1,74 @@ +package diff + +import ( + "fmt" +) + +var ( + // ErrTypeMismatch Compared types do not match + ErrTypeMismatch = NewError("types do not match") + // ErrInvalidChangeType The specified change values are not unsupported + ErrInvalidChangeType = NewError("change type must be one of 'create' or 'delete'") +) + +//our own version of an error, which can wrap others +type DiffError struct { + count int + message string + next error +} + +//Unwrap implement 1.13 unwrap feature for compatibility +func (s *DiffError) Unwrap() error { + return s.next +} + +//Error implements the error interface +func (s DiffError) Error() string { + cause := "" + if s.next != nil { + cause = s.next.Error() + } + return fmt.Sprintf(" %s (cause count %d)\n%s", s.message, s.count, cause) +} + +//AppendCause appends a new cause error to the chain +func (s *DiffError) WithCause(err error) *DiffError { + if s != nil && err != nil { + s.count++ + if s.next != nil { + if v, ok := err.(DiffError); ok { + s.next = v.WithCause(s.next) + } else if v, ok := err.(*DiffError); ok { + s.next = v.WithCause(s.next) + } else { + v = &DiffError{ + message: "auto wrapped error", + next: err, + } + s.next = v.WithCause(s.next) + } + } else { + s.next = err + } + } + return s +} + +//NewErrorf just give me a plain error with formatting +func NewErrorf(format string, messages ...interface{}) *DiffError { + return &DiffError{ + message: fmt.Sprintf(format, messages...), + } +} + +//NewError just give me a plain error +func NewError(message string, causes ...error) *DiffError { + s := &DiffError{ + message: message, + } + for _, cause := range causes { + s.WithCause(cause) // nolint: errcheck + } + return s +} diff --git a/vendor/github.com/r3labs/diff/v3/filter.go b/vendor/github.com/r3labs/diff/v3/filter.go new file mode 100644 index 00000000..12e549a6 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/filter.go @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package diff + +import "regexp" + +func pathmatch(filter, path []string) bool { + for i, f := range filter { + if len(path) < i+1 { + return false + } + + matched, _ := regexp.MatchString(f, path[i]) + if !matched { + return false + } + } + + return true +} diff --git a/vendor/github.com/r3labs/diff/v3/options.go b/vendor/github.com/r3labs/diff/v3/options.go new file mode 100644 index 00000000..fadbe86e --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/options.go @@ -0,0 +1,93 @@ +package diff + +// ConvertTypes enables values that are convertible to the target type to be converted when patching +func ConvertCompatibleTypes() func(d *Differ) error { + return func(d *Differ) error { + d.ConvertCompatibleTypes = true + return nil + } +} + +// FlattenEmbeddedStructs determines whether fields of embedded structs should behave as if they are directly under the parent +func FlattenEmbeddedStructs() func(d *Differ) error { + return func(d *Differ) error { + d.FlattenEmbeddedStructs = true + return nil + } +} + +// SliceOrdering determines whether the ordering of items in a slice results in a change +func SliceOrdering(enabled bool) func(d *Differ) error { + return func(d *Differ) error { + d.SliceOrdering = enabled + return nil + } +} + +// TagName sets the tag name to use when getting field names and options +func TagName(tag string) func(d *Differ) error { + return func(d *Differ) error { + d.TagName = tag + return nil + } +} + +// DisableStructValues disables populating a separate change for each item in a struct, +// where the struct is being compared to a nil value +func DisableStructValues() func(d *Differ) error { + return func(d *Differ) error { + d.DisableStructValues = true + return nil + } +} + +// CustomValueDiffers allows you to register custom differs for specific types +func CustomValueDiffers(vd ...ValueDiffer) func(d *Differ) error { + return func(d *Differ) error { + d.customValueDiffers = append(d.customValueDiffers, vd...) + for k := range d.customValueDiffers { + d.customValueDiffers[k].InsertParentDiffer(d.diff) + } + return nil + } +} + +// AllowTypeMismatch changed behaviour to report value as "updated" when its type has changed instead of error +func AllowTypeMismatch(enabled bool) func(d *Differ) error { + return func(d *Differ) error { + d.AllowTypeMismatch = enabled + return nil + } +} + +//StructMapKeySupport - Changelog paths do not provided structured object values for maps that contain complex +//keys (such as other structs). You must enable this support via an option and it then uses msgpack to encode +//path elements that are structs. If you don't have this on, and try to patch, your apply will fail for that +//element. +func StructMapKeySupport() func(d *Differ) error { + return func(d *Differ) error { + d.StructMapKeys = true + return nil + } +} + +//DiscardComplexOrigin - by default, we are now keeping the complex struct associated with a create entry. +//This allows us to fix the merge to new object issue of not having enough change log details when allocating +//new objects. This however is a trade off of memory size and complexity vs correctness which is often only +//necessary when embedding structs in slices and arrays. It memory constrained environments, it may be desirable +//to turn this feature off however from a computational perspective, keeping the complex origin is actually quite +//cheap so, make sure you're extremely clear on the pitfalls of turning this off prior to doing so. +func DiscardComplexOrigin() func(d *Differ) error { + return func(d *Differ) error { + d.DiscardParent = true + return nil + } +} + +// Filter allows you to determine which fields the differ descends into +func Filter(f FilterFunc) func(d *Differ) error { + return func(d *Differ) error { + d.Filter = f + return nil + } +} diff --git a/vendor/github.com/r3labs/diff/v3/patch.go b/vendor/github.com/r3labs/diff/v3/patch.go new file mode 100644 index 00000000..5814c536 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/patch.go @@ -0,0 +1,237 @@ +package diff + +import ( + "reflect" +) + +/** + This is a method of applying a changelog to a value or struct. change logs + should be generated with Diff and never manually created. This DOES NOT + apply fuzzy logic as would be in the case of a text patch. It does however + have a few additional features added to our struct tags. + + 1) create. This tag on a struct field indicates that the patch should + create the value if it's not there. I.e. if it's nil. This works for + pointers, maps and slices. + + 2) omitunequal. Generally, you don't want to do this, the expectation is + that if an item isn't there, you want to add it. For example, if your + diff shows an array element at index 6 is a string 'hello' but your target + only has 3 elements, none of them matching... you want to add 'hello' + regardless of the index. (think in a distributed context, another process + may have deleted more than one entry and 'hello' may no longer be in that + indexed spot. + + So given this scenario, the default behavior is to scan for the previous + value and replace it anyway, or simply append the new value. For maps the + default behavior is to simply add the key if it doesn't match. + + However, if you don't like the default behavior, and add the omitunequal + tag to your struct, patch will *NOT* update an array or map with the key + or array value unless they key or index contains a 'match' to the + previous value. In which case it will skip over that change. + + Patch is implemented as a best effort algorithm. That means you can receive + multiple nested errors and still successfully have a modified target. This + may even be acceptable depending on your use case. So keep in mind, just + because err != nil *DOESN'T* mean that the patch didn't accomplish your goal + in setting those changes that are actually available. For example, you may + diff two structs of the same type, then attempt to apply to an entirely + different struct that is similar in constitution (think interface here) and + you may in fact get all of the values populated you wished to anyway. +*/ + +//Not strictly necessary but might be nice in some cases +//go:generate stringer -type=PatchFlags +type PatchFlags uint32 + +const ( + OptionCreate PatchFlags = 1 << iota + OptionNoCreate + OptionOmitUnequal + OptionImmutable + FlagInvalidTarget + FlagApplied + FlagFailed + FlagCreated + FlagIgnored + FlagDeleted + FlagUpdated + FlagParentSetApplied + FlagParentSetFailed +) + +//PatchLogEntry defines how a DiffLog entry was applied +type PatchLogEntry struct { + Path []string `json:"path"` + From interface{} `json:"from"` + To interface{} `json:"to"` + Flags PatchFlags `json:"flags"` + Errors error `json:"errors"` +} +type PatchLog []PatchLogEntry + +//HasFlag - convenience function for users +func (p PatchLogEntry) HasFlag(flag PatchFlags) bool { + return (p.Flags & flag) != 0 +} + +//Applied - returns true if all change log entries were actually +// applied, regardless of if any errors were encountered +func (p PatchLog) Applied() bool { + if p.HasErrors() { + for _, ple := range p { + if !ple.HasFlag(FlagApplied) { + return false + } + } + } + return true +} + +//HasErrors - indicates if a patch log contains any errors +func (p PatchLog) HasErrors() (ret bool) { + for _, ple := range p { + if ple.Errors != nil { + ret = true + } + } + return +} + +//ErrorCount -- counts the number of errors encountered while patching +func (p PatchLog) ErrorCount() (ret uint) { + for _, ple := range p { + if ple.Errors != nil { + ret++ + } + } + return +} + +func Merge(original interface{}, changed interface{}, target interface{}) (PatchLog, error) { + d, _ := NewDiffer() + return d.Merge(original, changed, target) +} + +// Merge is a convenience function that diffs, the original and changed items +// and merges said changes with target all in one call. +func (d *Differ) Merge(original interface{}, changed interface{}, target interface{}) (PatchLog, error) { + StructMapKeySupport()(d) // nolint: errcheck + if cl, err := d.Diff(original, changed); err == nil { + return Patch(cl, target), nil + } else { + return nil, err + } +} + +func Patch(cl Changelog, target interface{}) (ret PatchLog) { + d, _ := NewDiffer() + return d.Patch(cl, target) +} + +//Patch... the missing feature. +func (d *Differ) Patch(cl Changelog, target interface{}) (ret PatchLog) { + for _, c := range cl { + ret = append(ret, NewPatchLogEntry(NewChangeValue(d, c, target))) + } + return ret +} + +//NewPatchLogEntry converts our complicated reflection based struct to +//a simpler format for the consumer +func NewPatchLogEntry(cv *ChangeValue) PatchLogEntry { + return PatchLogEntry{ + Path: cv.change.Path, + From: cv.change.From, + To: cv.change.To, + Flags: cv.flags, + Errors: cv.err, + } +} + +//NewChangeValue idiomatic constructor (also invokes render) +func NewChangeValue(d *Differ, c Change, target interface{}) (ret *ChangeValue) { + val := reflect.ValueOf(target) + ret = &ChangeValue{ + target: &val, + change: &c, + } + d.renderChangeTarget(ret) + return +} + +//renderChangeValue applies 'path' in change to target. nil check is foregone +// here as we control usage +func (d *Differ) renderChangeTarget(c *ChangeValue) { + //This particular change element may potentially have the immutable flag + if c.HasFlag(OptionImmutable) { + c.AddError(NewError("Option immutable set, cannot apply change")) + return + } //the we always set a failure, and only unset if we successfully render the element + c.SetFlag(FlagInvalidTarget) + + //substitute and solve for t (path) + switch c.target.Kind() { + + //path element that is a map + case reflect.Map: + //map elements are 'copies' and immutable so if we set the new value to the + //map prior to editing the value, it will fail to stick. To fix this, we + //defer the safe until the stack unwinds + m, k, v := d.renderMap(c) + defer d.updateMapEntry(c, m, k, v) + + //path element that is a slice + case reflect.Slice: + d.renderSlice(c) + + //walking a path means dealing with real elements + case reflect.Interface, reflect.Ptr: + if c.target.IsNil() { + n := reflect.New(c.target.Type().Elem()) + c.target.Set(n) + c.target = &n + d.renderChangeTarget(c) + return + } + + el := c.target.Elem() + c.target = &el + c.ClearFlag(FlagInvalidTarget) + + //path element that is a struct + case reflect.Struct: + d.patchStruct(c) + } + + //if for some reason, rendering this element fails, c will no longer be valid + //we are best effort though, so we keep on trucking + if !c.IsValid() { + c.AddError(NewErrorf("Unable to access path position %d. Target field is invalid", c.pos)) + } + + //we've taken care of this path element, are there any more? if so, process + //else, let's take some action + if c.pos < len(c.change.Path) && !c.HasFlag(FlagInvalidTarget) { + d.renderChangeTarget(c) + + } else { //we're at the end of the line... set the Value + switch c.change.Type { + case DELETE: + switch c.ParentKind() { + case reflect.Slice: + d.deleteSliceEntry(c) + case reflect.Struct: + d.deleteStructEntry(c) + default: + c.SetFlag(FlagIgnored) + } + case UPDATE, CREATE: + // this is generic because... we only deal in primitives here. AND + // the diff format To field already contains the correct type. + c.Set(reflect.ValueOf(c.change.To), d.ConvertCompatibleTypes) + c.SetFlag(FlagUpdated) + } + } +} diff --git a/vendor/github.com/r3labs/diff/v3/patch_map.go b/vendor/github.com/r3labs/diff/v3/patch_map.go new file mode 100644 index 00000000..6c3f0352 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/patch_map.go @@ -0,0 +1,106 @@ +package diff + +import ( + "errors" + "reflect" + + "github.com/vmihailenco/msgpack/v5" +) + +// renderMap - handle map rendering for patch +func (d *Differ) renderMap(c *ChangeValue) (m, k, v *reflect.Value) { + //we must tease out the type of the key, we use the msgpack from diff to recreate the key + kt := c.target.Type().Key() + field := reflect.New(kt) + + if d.StructMapKeys { + if err := msgpack.Unmarshal([]byte(c.change.Path[c.pos]), field.Interface()); err != nil { + c.SetFlag(FlagIgnored) + c.AddError(NewError("Unable to unmarshal path element to target type for key in map", err)) + return + } + c.key = field.Elem() + } else { + c.key = reflect.ValueOf(c.change.Path[c.pos]) + } + + if c.target.IsNil() && c.target.IsValid() { + c.target.Set(reflect.MakeMap(c.target.Type())) + } + + // we need to check that MapIndex does not panic here + // when the key type is not a string + defer func() { + if err := recover(); err != nil { + switch x := err.(type) { + case error: + c.AddError(NewError("Unable to unmarshal path element to target type for key in map", x)) + case string: + c.AddError(NewError("Unable to unmarshal path element to target type for key in map", errors.New(x))) + } + c.SetFlag(FlagIgnored) + } + }() + + x := c.target.MapIndex(c.key) + + if !x.IsValid() && c.change.Type != DELETE && !c.HasFlag(OptionNoCreate) { + x = c.NewElement() + } + if x.IsValid() { //Map elements come out as read only so we must convert + nv := reflect.New(x.Type()).Elem() + nv.Set(x) + x = nv + } + + if x.IsValid() && !reflect.DeepEqual(c.change.From, x.Interface()) && + c.HasFlag(OptionOmitUnequal) { + c.SetFlag(FlagIgnored) + c.AddError(NewError("target change doesn't match original")) + return + } + mp := *c.target //these may change out from underneath us as we recurse + key := c.key //so we make copies and pass back pointers to them + c.swap(&x) + + return &mp, &key, &x + +} + +// updateMapEntry - deletes are special, they are handled differently based on options +// +// container type etc. We have to have special handling for each +// type. Set values are more generic even if they must be instanced +func (d *Differ) updateMapEntry(c *ChangeValue, m, k, v *reflect.Value) { + if k == nil || m == nil { + return + } + + switch c.change.Type { + case DELETE: + if c.HasFlag(FlagDeleted) { + return + } + + if !m.CanSet() && v.IsValid() && v.Kind() == reflect.Struct { + for x := 0; x < v.NumField(); x++ { + if !v.Field(x).IsZero() { + m.SetMapIndex(*k, *v) + return + } + } //if all the fields are zero, remove from map + } + + m.SetMapIndex(*k, reflect.Value{}) + c.SetFlag(FlagDeleted) + + case CREATE: + m.SetMapIndex(*k, *v) + c.SetFlag(FlagCreated) + + case UPDATE: + m.SetMapIndex(*k, *v) + c.SetFlag(FlagUpdated) + + } +} diff --git a/vendor/github.com/r3labs/diff/v3/patch_slice.go b/vendor/github.com/r3labs/diff/v3/patch_slice.go new file mode 100644 index 00000000..9a703e57 --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/patch_slice.go @@ -0,0 +1,78 @@ +package diff + +/** + Types are being split out to more closely follow the library structure already + in place. Keeps the file simpler as well. +*/ +import ( + "reflect" + "strconv" +) + +//renderSlice - handle slice rendering for patch +func (d *Differ) renderSlice(c *ChangeValue) { + + var err error + field := c.change.Path[c.pos] + + //field better be an index of the slice + if c.index, err = strconv.Atoi(field); err != nil { + //if struct element is has identifier, use it instead + if identifier(d.TagName, reflect.Zero(c.target.Type().Elem())) != nil { + for c.index = 0; c.index < c.Len(); c.index++ { + if identifier(d.TagName, c.Index(c.index)) == field { + break + } + } + } else { + c.AddError(NewErrorf("invalid index in path. %s is not a number", field). + WithCause(err)) + } + } + var x reflect.Value + if c.Len() > c.index { + x = c.Index(c.index) + } else if c.change.Type == CREATE && !c.HasFlag(OptionNoCreate) { + x = c.NewArrayElement() + } + if !x.IsValid() { + if !c.HasFlag(OptionOmitUnequal) { + c.AddError(NewErrorf("Value index %d is invalid", c.index). + WithCause(NewError("scanning for Value index"))) + for c.index = 0; c.index < c.Len(); c.index++ { + y := c.Index(c.index) + if reflect.DeepEqual(y, c.change.From) { + c.AddError(NewErrorf("Value changed index to %d", c.index)) + x = y + break + } + } + } + } + if !x.IsValid() && c.change.Type != DELETE && !c.HasFlag(OptionNoCreate) { + x = c.NewArrayElement() + } + if !x.IsValid() && c.change.Type == DELETE { + c.index = -1 //no existing element to delete so don't bother + } + c.swap(&x) //containers must swap out the parent Value +} + +//deleteSliceEntry - deletes are special, they are handled differently based on options +// container type etc. We have to have special handling for each +// type. Set values are more generic even if they must be instanced +func (d *Differ) deleteSliceEntry(c *ChangeValue) { + //for a slice with only one element + if c.ParentLen() == 1 && c.index != -1 { + c.ParentSet(reflect.MakeSlice(c.parent.Type(), 0, 0), d.ConvertCompatibleTypes) + c.SetFlag(FlagDeleted) + //for a slice with multiple elements + } else if c.index != -1 { //this is an array delete the element from the parent + c.ParentIndex(c.index).Set(c.ParentIndex(c.ParentLen() - 1)) + c.ParentSet(c.parent.Slice(0, c.ParentLen()-1), d.ConvertCompatibleTypes) + c.SetFlag(FlagDeleted) + //for other slice elements, we ignore + } else { + c.SetFlag(FlagIgnored) + } +} diff --git a/vendor/github.com/r3labs/diff/v3/patch_struct.go b/vendor/github.com/r3labs/diff/v3/patch_struct.go new file mode 100644 index 00000000..4e2d247f --- /dev/null +++ b/vendor/github.com/r3labs/diff/v3/patch_struct.go @@ -0,0 +1,66 @@ +package diff + +import "reflect" + +/** + Types are being split out to more closely follow the library structure already + in place. Keeps the file simpler as well. +*/ + +type structField struct { + f reflect.StructField + v reflect.Value +} + +func getNestedFields(v reflect.Value, flattenEmbedded bool) []structField { + fields := make([]structField, 0) + + for i := 0; i < v.NumField(); i++ { + f := v.Type().Field(i) + fv := v.Field(i) + + if fv.Kind() == reflect.Struct && f.Anonymous && flattenEmbedded { + fields = append(fields, getNestedFields(fv, flattenEmbedded)...) + } else { + fields = append(fields, structField{f, fv}) + } + } + + return fields +} + +//patchStruct - handles the rendering of a struct field +func (d *Differ) patchStruct(c *ChangeValue) { + + field := c.change.Path[c.pos] + + structFields := getNestedFields(*c.target, d.FlattenEmbeddedStructs) + for _, structField := range structFields { + f := structField.f + tname := tagName(d.TagName, f) + if tname == "-" { + continue + } + if tname == field || f.Name == field { + x := structField.v + if hasTagOption(d.TagName, f, "nocreate") { + c.SetFlag(OptionNoCreate) + } + if hasTagOption(d.TagName, f, "omitunequal") { + c.SetFlag(OptionOmitUnequal) + } + if hasTagOption(d.TagName, f, "immutable") { + c.SetFlag(OptionImmutable) + } + c.swap(&x) + break + } + } +} + +//track and zero out struct members +func (d *Differ) deleteStructEntry(c *ChangeValue) { + + //deleting a struct value set's it to the 'basic' type + c.Set(reflect.Zero(c.target.Type()), d.ConvertCompatibleTypes) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/.prettierrc b/vendor/github.com/vmihailenco/msgpack/v5/.prettierrc new file mode 100644 index 00000000..8b7f044a --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/.prettierrc @@ -0,0 +1,4 @@ +semi: false +singleQuote: true +proseWrap: always +printWidth: 100 diff --git a/vendor/github.com/vmihailenco/msgpack/v5/.travis.yml b/vendor/github.com/vmihailenco/msgpack/v5/.travis.yml new file mode 100644 index 00000000..e2ce06c4 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: go + +go: + - 1.15.x + - 1.16.x + - tip + +matrix: + allow_failures: + - go: tip + +env: + - GO111MODULE=on + +go_import_path: github.com/vmihailenco/msgpack + +before_install: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go + env GOPATH)/bin v1.31.0 diff --git a/vendor/github.com/vmihailenco/msgpack/v5/CHANGELOG.md b/vendor/github.com/vmihailenco/msgpack/v5/CHANGELOG.md new file mode 100644 index 00000000..f6b19d5b --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/CHANGELOG.md @@ -0,0 +1,51 @@ +## [5.3.5](https://github.com/vmihailenco/msgpack/compare/v5.3.4...v5.3.5) (2021-10-22) + + + +## v5 + +### Added + +- `DecodeMap` is split into `DecodeMap`, `DecodeTypedMap`, and `DecodeUntypedMap`. +- New msgpack extensions API. + +### Changed + +- `Reset*` functions also reset flags. +- `SetMapDecodeFunc` is renamed to `SetMapDecoder`. +- `StructAsArray` is renamed to `UseArrayEncodedStructs`. +- `SortMapKeys` is renamed to `SetSortMapKeys`. + +### Removed + +- `UseJSONTag` is removed. Use `SetCustomStructTag("json")` instead. + +## v4 + +- Encode, Decode, Marshal, and Unmarshal are changed to accept single argument. EncodeMulti and + DecodeMulti are added as replacement. +- Added EncodeInt8/16/32/64 and EncodeUint8/16/32/64. +- Encoder changed to preserve type of numbers instead of chosing most compact encoding. The old + behavior can be achieved with Encoder.UseCompactEncoding. + +## v3.3 + +- `msgpack:",inline"` tag is restored to force inlining structs. + +## v3.2 + +- Decoding extension types returns pointer to the value instead of the value. Fixes #153 + +## v3 + +- gopkg.in is not supported any more. Update import path to github.com/vmihailenco/msgpack. +- Msgpack maps are decoded into map[string]interface{} by default. +- EncodeSliceLen is removed in favor of EncodeArrayLen. DecodeSliceLen is removed in favor of + DecodeArrayLen. +- Embedded structs are automatically inlined where possible. +- Time is encoded using extension as described in https://github.com/msgpack/msgpack/pull/209. Old + format is supported as well. +- EncodeInt8/16/32/64 is replaced with EncodeInt. EncodeUint8/16/32/64 is replaced with EncodeUint. + There should be no performance differences. +- DecodeInterface can now return int8/16/32 and uint8/16/32. +- PeekCode returns codes.Code instead of byte. diff --git a/vendor/github.com/vmihailenco/msgpack/v5/LICENSE b/vendor/github.com/vmihailenco/msgpack/v5/LICENSE new file mode 100644 index 00000000..b749d070 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013 The github.com/vmihailenco/msgpack Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/vmihailenco/msgpack/v5/Makefile b/vendor/github.com/vmihailenco/msgpack/v5/Makefile new file mode 100644 index 00000000..e9aade78 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/Makefile @@ -0,0 +1,6 @@ +test: + go test ./... + go test ./... -short -race + go test ./... -run=NONE -bench=. -benchmem + env GOOS=linux GOARCH=386 go test ./... + go vet diff --git a/vendor/github.com/vmihailenco/msgpack/v5/README.md b/vendor/github.com/vmihailenco/msgpack/v5/README.md new file mode 100644 index 00000000..66ad98b9 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/README.md @@ -0,0 +1,86 @@ +# MessagePack encoding for Golang + +[![Build Status](https://travis-ci.org/vmihailenco/msgpack.svg)](https://travis-ci.org/vmihailenco/msgpack) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/vmihailenco/msgpack/v5)](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5) +[![Documentation](https://img.shields.io/badge/msgpack-documentation-informational)](https://msgpack.uptrace.dev/) +[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) + +> :heart: +> [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev/?utm_source=gh-msgpack&utm_campaign=gh-msgpack-var2) + +- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions. +- [Documentation](https://msgpack.uptrace.dev) +- [Reference](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5) +- [Examples](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#pkg-examples) + +Other projects you may like: + +- [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite. +- [BunRouter](https://bunrouter.uptrace.dev/) - fast and flexible HTTP router for Go. + +## Features + +- Primitives, arrays, maps, structs, time.Time and interface{}. +- Appengine \*datastore.Key and datastore.Cursor. +- [CustomEncoder]/[CustomDecoder] interfaces for custom encoding. +- [Extensions](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-RegisterExt) to encode + type information. +- Renaming fields via `msgpack:"my_field_name"` and alias via `msgpack:"alias:another_name"`. +- Omitting individual empty fields via `msgpack:",omitempty"` tag or all + [empty fields in a struct](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Marshal-OmitEmpty). +- [Map keys sorting](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.SetSortMapKeys). +- Encoding/decoding all + [structs as arrays](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.UseArrayEncodedStructs) + or + [individual structs](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Marshal-AsArray). +- [Encoder.SetCustomStructTag] with [Decoder.SetCustomStructTag] can turn msgpack into drop-in + replacement for any tag. +- Simple but very fast and efficient + [queries](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Decoder.Query). + +[customencoder]: https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#CustomEncoder +[customdecoder]: https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#CustomDecoder +[encoder.setcustomstructtag]: + https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.SetCustomStructTag +[decoder.setcustomstructtag]: + https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Decoder.SetCustomStructTag + +## Installation + +msgpack supports 2 last Go versions and requires support for +[Go modules](https://github.com/golang/go/wiki/Modules). So make sure to initialize a Go module: + +```shell +go mod init github.com/my/repo +``` + +And then install msgpack/v5 (note _v5_ in the import; omitting it is a popular mistake): + +```shell +go get github.com/vmihailenco/msgpack/v5 +``` + +## Quickstart + +```go +import "github.com/vmihailenco/msgpack/v5" + +func ExampleMarshal() { + type Item struct { + Foo string + } + + b, err := msgpack.Marshal(&Item{Foo: "bar"}) + if err != nil { + panic(err) + } + + var item Item + err = msgpack.Unmarshal(b, &item) + if err != nil { + panic(err) + } + fmt.Println(item.Foo) + // Output: bar +} +``` diff --git a/vendor/github.com/vmihailenco/msgpack/v5/commitlint.config.js b/vendor/github.com/vmihailenco/msgpack/v5/commitlint.config.js new file mode 100644 index 00000000..4fedde6d --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/commitlint.config.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] } diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode.go b/vendor/github.com/vmihailenco/msgpack/v5/decode.go new file mode 100644 index 00000000..5df40e5d --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode.go @@ -0,0 +1,663 @@ +package msgpack + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "reflect" + "sync" + "time" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +const ( + looseInterfaceDecodingFlag uint32 = 1 << iota + disallowUnknownFieldsFlag +) + +const ( + bytesAllocLimit = 1e6 // 1mb + sliceAllocLimit = 1e4 + maxMapSize = 1e6 +) + +type bufReader interface { + io.Reader + io.ByteScanner +} + +//------------------------------------------------------------------------------ + +var decPool = sync.Pool{ + New: func() interface{} { + return NewDecoder(nil) + }, +} + +func GetDecoder() *Decoder { + return decPool.Get().(*Decoder) +} + +func PutDecoder(dec *Decoder) { + dec.r = nil + dec.s = nil + decPool.Put(dec) +} + +//------------------------------------------------------------------------------ + +// Unmarshal decodes the MessagePack-encoded data and stores the result +// in the value pointed to by v. +func Unmarshal(data []byte, v interface{}) error { + dec := GetDecoder() + + dec.Reset(bytes.NewReader(data)) + err := dec.Decode(v) + + PutDecoder(dec) + + return err +} + +// A Decoder reads and decodes MessagePack values from an input stream. +type Decoder struct { + r io.Reader + s io.ByteScanner + buf []byte + + rec []byte // accumulates read data if not nil + + dict []string + flags uint32 + structTag string + mapDecoder func(*Decoder) (interface{}, error) +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read data from r +// beyond the requested msgpack values. Buffering can be disabled +// by passing a reader that implements io.ByteScanner interface. +func NewDecoder(r io.Reader) *Decoder { + d := new(Decoder) + d.Reset(r) + return d +} + +// Reset discards any buffered data, resets all state, and switches the buffered +// reader to read from r. +func (d *Decoder) Reset(r io.Reader) { + d.ResetDict(r, nil) +} + +// ResetDict is like Reset, but also resets the dict. +func (d *Decoder) ResetDict(r io.Reader, dict []string) { + d.resetReader(r) + d.flags = 0 + d.structTag = "" + d.mapDecoder = nil + d.dict = dict +} + +func (d *Decoder) WithDict(dict []string, fn func(*Decoder) error) error { + oldDict := d.dict + d.dict = dict + err := fn(d) + d.dict = oldDict + return err +} + +func (d *Decoder) resetReader(r io.Reader) { + if br, ok := r.(bufReader); ok { + d.r = br + d.s = br + } else { + br := bufio.NewReader(r) + d.r = br + d.s = br + } +} + +func (d *Decoder) SetMapDecoder(fn func(*Decoder) (interface{}, error)) { + d.mapDecoder = fn +} + +// UseLooseInterfaceDecoding causes decoder to use DecodeInterfaceLoose +// to decode msgpack value into Go interface{}. +func (d *Decoder) UseLooseInterfaceDecoding(on bool) { + if on { + d.flags |= looseInterfaceDecodingFlag + } else { + d.flags &= ^looseInterfaceDecodingFlag + } +} + +// SetCustomStructTag causes the decoder to use the supplied tag as a fallback option +// if there is no msgpack tag. +func (d *Decoder) SetCustomStructTag(tag string) { + d.structTag = tag +} + +// DisallowUnknownFields causes the Decoder to return an error when the destination +// is a struct and the input contains object keys which do not match any +// non-ignored, exported fields in the destination. +func (d *Decoder) DisallowUnknownFields(on bool) { + if on { + d.flags |= disallowUnknownFieldsFlag + } else { + d.flags &= ^disallowUnknownFieldsFlag + } +} + +// UseInternedStrings enables support for decoding interned strings. +func (d *Decoder) UseInternedStrings(on bool) { + if on { + d.flags |= useInternedStringsFlag + } else { + d.flags &= ^useInternedStringsFlag + } +} + +// Buffered returns a reader of the data remaining in the Decoder's buffer. +// The reader is valid until the next call to Decode. +func (d *Decoder) Buffered() io.Reader { + return d.r +} + +//nolint:gocyclo +func (d *Decoder) Decode(v interface{}) error { + var err error + switch v := v.(type) { + case *string: + if v != nil { + *v, err = d.DecodeString() + return err + } + case *[]byte: + if v != nil { + return d.decodeBytesPtr(v) + } + case *int: + if v != nil { + *v, err = d.DecodeInt() + return err + } + case *int8: + if v != nil { + *v, err = d.DecodeInt8() + return err + } + case *int16: + if v != nil { + *v, err = d.DecodeInt16() + return err + } + case *int32: + if v != nil { + *v, err = d.DecodeInt32() + return err + } + case *int64: + if v != nil { + *v, err = d.DecodeInt64() + return err + } + case *uint: + if v != nil { + *v, err = d.DecodeUint() + return err + } + case *uint8: + if v != nil { + *v, err = d.DecodeUint8() + return err + } + case *uint16: + if v != nil { + *v, err = d.DecodeUint16() + return err + } + case *uint32: + if v != nil { + *v, err = d.DecodeUint32() + return err + } + case *uint64: + if v != nil { + *v, err = d.DecodeUint64() + return err + } + case *bool: + if v != nil { + *v, err = d.DecodeBool() + return err + } + case *float32: + if v != nil { + *v, err = d.DecodeFloat32() + return err + } + case *float64: + if v != nil { + *v, err = d.DecodeFloat64() + return err + } + case *[]string: + return d.decodeStringSlicePtr(v) + case *map[string]string: + return d.decodeMapStringStringPtr(v) + case *map[string]interface{}: + return d.decodeMapStringInterfacePtr(v) + case *time.Duration: + if v != nil { + vv, err := d.DecodeInt64() + *v = time.Duration(vv) + return err + } + case *time.Time: + if v != nil { + *v, err = d.DecodeTime() + return err + } + } + + vv := reflect.ValueOf(v) + if !vv.IsValid() { + return errors.New("msgpack: Decode(nil)") + } + if vv.Kind() != reflect.Ptr { + return fmt.Errorf("msgpack: Decode(non-pointer %T)", v) + } + if vv.IsNil() { + return fmt.Errorf("msgpack: Decode(non-settable %T)", v) + } + + vv = vv.Elem() + if vv.Kind() == reflect.Interface { + if !vv.IsNil() { + vv = vv.Elem() + if vv.Kind() != reflect.Ptr { + return fmt.Errorf("msgpack: Decode(non-pointer %s)", vv.Type().String()) + } + } + } + + return d.DecodeValue(vv) +} + +func (d *Decoder) DecodeMulti(v ...interface{}) error { + for _, vv := range v { + if err := d.Decode(vv); err != nil { + return err + } + } + return nil +} + +func (d *Decoder) decodeInterfaceCond() (interface{}, error) { + if d.flags&looseInterfaceDecodingFlag != 0 { + return d.DecodeInterfaceLoose() + } + return d.DecodeInterface() +} + +func (d *Decoder) DecodeValue(v reflect.Value) error { + decode := getDecoder(v.Type()) + return decode(d, v) +} + +func (d *Decoder) DecodeNil() error { + c, err := d.readCode() + if err != nil { + return err + } + if c != msgpcode.Nil { + return fmt.Errorf("msgpack: invalid code=%x decoding nil", c) + } + return nil +} + +func (d *Decoder) decodeNilValue(v reflect.Value) error { + err := d.DecodeNil() + if v.IsNil() { + return err + } + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + v.Set(reflect.Zero(v.Type())) + return err +} + +func (d *Decoder) DecodeBool() (bool, error) { + c, err := d.readCode() + if err != nil { + return false, err + } + return d.bool(c) +} + +func (d *Decoder) bool(c byte) (bool, error) { + if c == msgpcode.Nil { + return false, nil + } + if c == msgpcode.False { + return false, nil + } + if c == msgpcode.True { + return true, nil + } + return false, fmt.Errorf("msgpack: invalid code=%x decoding bool", c) +} + +func (d *Decoder) DecodeDuration() (time.Duration, error) { + n, err := d.DecodeInt64() + if err != nil { + return 0, err + } + return time.Duration(n), nil +} + +// DecodeInterface decodes value into interface. It returns following types: +// - nil, +// - bool, +// - int8, int16, int32, int64, +// - uint8, uint16, uint32, uint64, +// - float32 and float64, +// - string, +// - []byte, +// - slices of any of the above, +// - maps of any of the above. +// +// DecodeInterface should be used only when you don't know the type of value +// you are decoding. For example, if you are decoding number it is better to use +// DecodeInt64 for negative numbers and DecodeUint64 for positive numbers. +func (d *Decoder) DecodeInterface() (interface{}, error) { + c, err := d.readCode() + if err != nil { + return nil, err + } + + if msgpcode.IsFixedNum(c) { + return int8(c), nil + } + if msgpcode.IsFixedMap(c) { + err = d.s.UnreadByte() + if err != nil { + return nil, err + } + return d.decodeMapDefault() + } + if msgpcode.IsFixedArray(c) { + return d.decodeSlice(c) + } + if msgpcode.IsFixedString(c) { + return d.string(c) + } + + switch c { + case msgpcode.Nil: + return nil, nil + case msgpcode.False, msgpcode.True: + return d.bool(c) + case msgpcode.Float: + return d.float32(c) + case msgpcode.Double: + return d.float64(c) + case msgpcode.Uint8: + return d.uint8() + case msgpcode.Uint16: + return d.uint16() + case msgpcode.Uint32: + return d.uint32() + case msgpcode.Uint64: + return d.uint64() + case msgpcode.Int8: + return d.int8() + case msgpcode.Int16: + return d.int16() + case msgpcode.Int32: + return d.int32() + case msgpcode.Int64: + return d.int64() + case msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32: + return d.bytes(c, nil) + case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32: + return d.string(c) + case msgpcode.Array16, msgpcode.Array32: + return d.decodeSlice(c) + case msgpcode.Map16, msgpcode.Map32: + err = d.s.UnreadByte() + if err != nil { + return nil, err + } + return d.decodeMapDefault() + case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16, + msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32: + return d.decodeInterfaceExt(c) + } + + return 0, fmt.Errorf("msgpack: unknown code %x decoding interface{}", c) +} + +// DecodeInterfaceLoose is like DecodeInterface except that: +// - int8, int16, and int32 are converted to int64, +// - uint8, uint16, and uint32 are converted to uint64, +// - float32 is converted to float64. +// - []byte is converted to string. +func (d *Decoder) DecodeInterfaceLoose() (interface{}, error) { + c, err := d.readCode() + if err != nil { + return nil, err + } + + if msgpcode.IsFixedNum(c) { + return int64(int8(c)), nil + } + if msgpcode.IsFixedMap(c) { + err = d.s.UnreadByte() + if err != nil { + return nil, err + } + return d.decodeMapDefault() + } + if msgpcode.IsFixedArray(c) { + return d.decodeSlice(c) + } + if msgpcode.IsFixedString(c) { + return d.string(c) + } + + switch c { + case msgpcode.Nil: + return nil, nil + case msgpcode.False, msgpcode.True: + return d.bool(c) + case msgpcode.Float, msgpcode.Double: + return d.float64(c) + case msgpcode.Uint8, msgpcode.Uint16, msgpcode.Uint32, msgpcode.Uint64: + return d.uint(c) + case msgpcode.Int8, msgpcode.Int16, msgpcode.Int32, msgpcode.Int64: + return d.int(c) + case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32, + msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32: + return d.string(c) + case msgpcode.Array16, msgpcode.Array32: + return d.decodeSlice(c) + case msgpcode.Map16, msgpcode.Map32: + err = d.s.UnreadByte() + if err != nil { + return nil, err + } + return d.decodeMapDefault() + case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16, + msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32: + return d.decodeInterfaceExt(c) + } + + return 0, fmt.Errorf("msgpack: unknown code %x decoding interface{}", c) +} + +// Skip skips next value. +func (d *Decoder) Skip() error { + c, err := d.readCode() + if err != nil { + return err + } + + if msgpcode.IsFixedNum(c) { + return nil + } + if msgpcode.IsFixedMap(c) { + return d.skipMap(c) + } + if msgpcode.IsFixedArray(c) { + return d.skipSlice(c) + } + if msgpcode.IsFixedString(c) { + return d.skipBytes(c) + } + + switch c { + case msgpcode.Nil, msgpcode.False, msgpcode.True: + return nil + case msgpcode.Uint8, msgpcode.Int8: + return d.skipN(1) + case msgpcode.Uint16, msgpcode.Int16: + return d.skipN(2) + case msgpcode.Uint32, msgpcode.Int32, msgpcode.Float: + return d.skipN(4) + case msgpcode.Uint64, msgpcode.Int64, msgpcode.Double: + return d.skipN(8) + case msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32: + return d.skipBytes(c) + case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32: + return d.skipBytes(c) + case msgpcode.Array16, msgpcode.Array32: + return d.skipSlice(c) + case msgpcode.Map16, msgpcode.Map32: + return d.skipMap(c) + case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16, + msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32: + return d.skipExt(c) + } + + return fmt.Errorf("msgpack: unknown code %x", c) +} + +func (d *Decoder) DecodeRaw() (RawMessage, error) { + d.rec = make([]byte, 0) + if err := d.Skip(); err != nil { + return nil, err + } + msg := RawMessage(d.rec) + d.rec = nil + return msg, nil +} + +// PeekCode returns the next MessagePack code without advancing the reader. +// Subpackage msgpack/codes defines the list of available msgpcode. +func (d *Decoder) PeekCode() (byte, error) { + c, err := d.s.ReadByte() + if err != nil { + return 0, err + } + return c, d.s.UnreadByte() +} + +// ReadFull reads exactly len(buf) bytes into the buf. +func (d *Decoder) ReadFull(buf []byte) error { + _, err := readN(d.r, buf, len(buf)) + return err +} + +func (d *Decoder) hasNilCode() bool { + code, err := d.PeekCode() + return err == nil && code == msgpcode.Nil +} + +func (d *Decoder) readCode() (byte, error) { + c, err := d.s.ReadByte() + if err != nil { + return 0, err + } + if d.rec != nil { + d.rec = append(d.rec, c) + } + return c, nil +} + +func (d *Decoder) readFull(b []byte) error { + _, err := io.ReadFull(d.r, b) + if err != nil { + return err + } + if d.rec != nil { + d.rec = append(d.rec, b...) + } + return nil +} + +func (d *Decoder) readN(n int) ([]byte, error) { + var err error + d.buf, err = readN(d.r, d.buf, n) + if err != nil { + return nil, err + } + if d.rec != nil { + // TODO: read directly into d.rec? + d.rec = append(d.rec, d.buf...) + } + return d.buf, nil +} + +func readN(r io.Reader, b []byte, n int) ([]byte, error) { + if b == nil { + if n == 0 { + return make([]byte, 0), nil + } + switch { + case n < 64: + b = make([]byte, 0, 64) + case n <= bytesAllocLimit: + b = make([]byte, 0, n) + default: + b = make([]byte, 0, bytesAllocLimit) + } + } + + if n <= cap(b) { + b = b[:n] + _, err := io.ReadFull(r, b) + return b, err + } + b = b[:cap(b)] + + var pos int + for { + alloc := min(n-len(b), bytesAllocLimit) + b = append(b, make([]byte, alloc)...) + + _, err := io.ReadFull(r, b[pos:]) + if err != nil { + return b, err + } + + if len(b) == n { + break + } + pos = len(b) + } + + return b, nil +} + +func min(a, b int) int { //nolint:unparam + if a <= b { + return a + } + return b +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_map.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_map.go new file mode 100644 index 00000000..52e0526c --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_map.go @@ -0,0 +1,339 @@ +package msgpack + +import ( + "errors" + "fmt" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +var errArrayStruct = errors.New("msgpack: number of fields in array-encoded struct has changed") + +var ( + mapStringStringPtrType = reflect.TypeOf((*map[string]string)(nil)) + mapStringStringType = mapStringStringPtrType.Elem() +) + +var ( + mapStringInterfacePtrType = reflect.TypeOf((*map[string]interface{})(nil)) + mapStringInterfaceType = mapStringInterfacePtrType.Elem() +) + +func decodeMapValue(d *Decoder, v reflect.Value) error { + n, err := d.DecodeMapLen() + if err != nil { + return err + } + + typ := v.Type() + if n == -1 { + v.Set(reflect.Zero(typ)) + return nil + } + + if v.IsNil() { + v.Set(reflect.MakeMap(typ)) + } + if n == 0 { + return nil + } + + return d.decodeTypedMapValue(v, n) +} + +func (d *Decoder) decodeMapDefault() (interface{}, error) { + if d.mapDecoder != nil { + return d.mapDecoder(d) + } + return d.DecodeMap() +} + +// DecodeMapLen decodes map length. Length is -1 when map is nil. +func (d *Decoder) DecodeMapLen() (int, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + + if msgpcode.IsExt(c) { + if err = d.skipExtHeader(c); err != nil { + return 0, err + } + + c, err = d.readCode() + if err != nil { + return 0, err + } + } + return d.mapLen(c) +} + +func (d *Decoder) mapLen(c byte) (int, error) { + if c == msgpcode.Nil { + return -1, nil + } + if c >= msgpcode.FixedMapLow && c <= msgpcode.FixedMapHigh { + return int(c & msgpcode.FixedMapMask), nil + } + if c == msgpcode.Map16 { + size, err := d.uint16() + return int(size), err + } + if c == msgpcode.Map32 { + size, err := d.uint32() + return int(size), err + } + return 0, unexpectedCodeError{code: c, hint: "map length"} +} + +func decodeMapStringStringValue(d *Decoder, v reflect.Value) error { + mptr := v.Addr().Convert(mapStringStringPtrType).Interface().(*map[string]string) + return d.decodeMapStringStringPtr(mptr) +} + +func (d *Decoder) decodeMapStringStringPtr(ptr *map[string]string) error { + size, err := d.DecodeMapLen() + if err != nil { + return err + } + if size == -1 { + *ptr = nil + return nil + } + + m := *ptr + if m == nil { + *ptr = make(map[string]string, min(size, maxMapSize)) + m = *ptr + } + + for i := 0; i < size; i++ { + mk, err := d.DecodeString() + if err != nil { + return err + } + mv, err := d.DecodeString() + if err != nil { + return err + } + m[mk] = mv + } + + return nil +} + +func decodeMapStringInterfaceValue(d *Decoder, v reflect.Value) error { + ptr := v.Addr().Convert(mapStringInterfacePtrType).Interface().(*map[string]interface{}) + return d.decodeMapStringInterfacePtr(ptr) +} + +func (d *Decoder) decodeMapStringInterfacePtr(ptr *map[string]interface{}) error { + m, err := d.DecodeMap() + if err != nil { + return err + } + *ptr = m + return nil +} + +func (d *Decoder) DecodeMap() (map[string]interface{}, error) { + n, err := d.DecodeMapLen() + if err != nil { + return nil, err + } + + if n == -1 { + return nil, nil + } + + m := make(map[string]interface{}, min(n, maxMapSize)) + + for i := 0; i < n; i++ { + mk, err := d.DecodeString() + if err != nil { + return nil, err + } + mv, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + m[mk] = mv + } + + return m, nil +} + +func (d *Decoder) DecodeUntypedMap() (map[interface{}]interface{}, error) { + n, err := d.DecodeMapLen() + if err != nil { + return nil, err + } + + if n == -1 { + return nil, nil + } + + m := make(map[interface{}]interface{}, min(n, maxMapSize)) + + for i := 0; i < n; i++ { + mk, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + + mv, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + + m[mk] = mv + } + + return m, nil +} + +// DecodeTypedMap decodes a typed map. Typed map is a map that has a fixed type for keys and values. +// Key and value types may be different. +func (d *Decoder) DecodeTypedMap() (interface{}, error) { + n, err := d.DecodeMapLen() + if err != nil { + return nil, err + } + if n <= 0 { + return nil, nil + } + + key, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + + value, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + + keyType := reflect.TypeOf(key) + valueType := reflect.TypeOf(value) + + if !keyType.Comparable() { + return nil, fmt.Errorf("msgpack: unsupported map key: %s", keyType.String()) + } + + mapType := reflect.MapOf(keyType, valueType) + mapValue := reflect.MakeMap(mapType) + mapValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) + + n-- + if err := d.decodeTypedMapValue(mapValue, n); err != nil { + return nil, err + } + + return mapValue.Interface(), nil +} + +func (d *Decoder) decodeTypedMapValue(v reflect.Value, n int) error { + typ := v.Type() + keyType := typ.Key() + valueType := typ.Elem() + + for i := 0; i < n; i++ { + mk := reflect.New(keyType).Elem() + if err := d.DecodeValue(mk); err != nil { + return err + } + + mv := reflect.New(valueType).Elem() + if err := d.DecodeValue(mv); err != nil { + return err + } + + v.SetMapIndex(mk, mv) + } + + return nil +} + +func (d *Decoder) skipMap(c byte) error { + n, err := d.mapLen(c) + if err != nil { + return err + } + for i := 0; i < n; i++ { + if err := d.Skip(); err != nil { + return err + } + if err := d.Skip(); err != nil { + return err + } + } + return nil +} + +func decodeStructValue(d *Decoder, v reflect.Value) error { + c, err := d.readCode() + if err != nil { + return err + } + + n, err := d.mapLen(c) + if err == nil { + return d.decodeStruct(v, n) + } + + var err2 error + n, err2 = d.arrayLen(c) + if err2 != nil { + return err + } + + if n <= 0 { + v.Set(reflect.Zero(v.Type())) + return nil + } + + fields := structs.Fields(v.Type(), d.structTag) + if n != len(fields.List) { + return errArrayStruct + } + + for _, f := range fields.List { + if err := f.DecodeValue(d, v); err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) decodeStruct(v reflect.Value, n int) error { + if n == -1 { + v.Set(reflect.Zero(v.Type())) + return nil + } + + fields := structs.Fields(v.Type(), d.structTag) + for i := 0; i < n; i++ { + name, err := d.decodeStringTemp() + if err != nil { + return err + } + + if f := fields.Map[name]; f != nil { + if err := f.DecodeValue(d, v); err != nil { + return err + } + continue + } + + if d.flags&disallowUnknownFieldsFlag != 0 { + return fmt.Errorf("msgpack: unknown field %q", name) + } + if err := d.Skip(); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_number.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_number.go new file mode 100644 index 00000000..45d6a741 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_number.go @@ -0,0 +1,295 @@ +package msgpack + +import ( + "fmt" + "math" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +func (d *Decoder) skipN(n int) error { + _, err := d.readN(n) + return err +} + +func (d *Decoder) uint8() (uint8, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return c, nil +} + +func (d *Decoder) int8() (int8, error) { + n, err := d.uint8() + return int8(n), err +} + +func (d *Decoder) uint16() (uint16, error) { + b, err := d.readN(2) + if err != nil { + return 0, err + } + return (uint16(b[0]) << 8) | uint16(b[1]), nil +} + +func (d *Decoder) int16() (int16, error) { + n, err := d.uint16() + return int16(n), err +} + +func (d *Decoder) uint32() (uint32, error) { + b, err := d.readN(4) + if err != nil { + return 0, err + } + n := (uint32(b[0]) << 24) | + (uint32(b[1]) << 16) | + (uint32(b[2]) << 8) | + uint32(b[3]) + return n, nil +} + +func (d *Decoder) int32() (int32, error) { + n, err := d.uint32() + return int32(n), err +} + +func (d *Decoder) uint64() (uint64, error) { + b, err := d.readN(8) + if err != nil { + return 0, err + } + n := (uint64(b[0]) << 56) | + (uint64(b[1]) << 48) | + (uint64(b[2]) << 40) | + (uint64(b[3]) << 32) | + (uint64(b[4]) << 24) | + (uint64(b[5]) << 16) | + (uint64(b[6]) << 8) | + uint64(b[7]) + return n, nil +} + +func (d *Decoder) int64() (int64, error) { + n, err := d.uint64() + return int64(n), err +} + +// DecodeUint64 decodes msgpack int8/16/32/64 and uint8/16/32/64 +// into Go uint64. +func (d *Decoder) DecodeUint64() (uint64, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.uint(c) +} + +func (d *Decoder) uint(c byte) (uint64, error) { + if c == msgpcode.Nil { + return 0, nil + } + if msgpcode.IsFixedNum(c) { + return uint64(int8(c)), nil + } + switch c { + case msgpcode.Uint8: + n, err := d.uint8() + return uint64(n), err + case msgpcode.Int8: + n, err := d.int8() + return uint64(n), err + case msgpcode.Uint16: + n, err := d.uint16() + return uint64(n), err + case msgpcode.Int16: + n, err := d.int16() + return uint64(n), err + case msgpcode.Uint32: + n, err := d.uint32() + return uint64(n), err + case msgpcode.Int32: + n, err := d.int32() + return uint64(n), err + case msgpcode.Uint64, msgpcode.Int64: + return d.uint64() + } + return 0, fmt.Errorf("msgpack: invalid code=%x decoding uint64", c) +} + +// DecodeInt64 decodes msgpack int8/16/32/64 and uint8/16/32/64 +// into Go int64. +func (d *Decoder) DecodeInt64() (int64, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.int(c) +} + +func (d *Decoder) int(c byte) (int64, error) { + if c == msgpcode.Nil { + return 0, nil + } + if msgpcode.IsFixedNum(c) { + return int64(int8(c)), nil + } + switch c { + case msgpcode.Uint8: + n, err := d.uint8() + return int64(n), err + case msgpcode.Int8: + n, err := d.uint8() + return int64(int8(n)), err + case msgpcode.Uint16: + n, err := d.uint16() + return int64(n), err + case msgpcode.Int16: + n, err := d.uint16() + return int64(int16(n)), err + case msgpcode.Uint32: + n, err := d.uint32() + return int64(n), err + case msgpcode.Int32: + n, err := d.uint32() + return int64(int32(n)), err + case msgpcode.Uint64, msgpcode.Int64: + n, err := d.uint64() + return int64(n), err + } + return 0, fmt.Errorf("msgpack: invalid code=%x decoding int64", c) +} + +func (d *Decoder) DecodeFloat32() (float32, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.float32(c) +} + +func (d *Decoder) float32(c byte) (float32, error) { + if c == msgpcode.Float { + n, err := d.uint32() + if err != nil { + return 0, err + } + return math.Float32frombits(n), nil + } + + n, err := d.int(c) + if err != nil { + return 0, fmt.Errorf("msgpack: invalid code=%x decoding float32", c) + } + return float32(n), nil +} + +// DecodeFloat64 decodes msgpack float32/64 into Go float64. +func (d *Decoder) DecodeFloat64() (float64, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.float64(c) +} + +func (d *Decoder) float64(c byte) (float64, error) { + switch c { + case msgpcode.Float: + n, err := d.float32(c) + if err != nil { + return 0, err + } + return float64(n), nil + case msgpcode.Double: + n, err := d.uint64() + if err != nil { + return 0, err + } + return math.Float64frombits(n), nil + } + + n, err := d.int(c) + if err != nil { + return 0, fmt.Errorf("msgpack: invalid code=%x decoding float32", c) + } + return float64(n), nil +} + +func (d *Decoder) DecodeUint() (uint, error) { + n, err := d.DecodeUint64() + return uint(n), err +} + +func (d *Decoder) DecodeUint8() (uint8, error) { + n, err := d.DecodeUint64() + return uint8(n), err +} + +func (d *Decoder) DecodeUint16() (uint16, error) { + n, err := d.DecodeUint64() + return uint16(n), err +} + +func (d *Decoder) DecodeUint32() (uint32, error) { + n, err := d.DecodeUint64() + return uint32(n), err +} + +func (d *Decoder) DecodeInt() (int, error) { + n, err := d.DecodeInt64() + return int(n), err +} + +func (d *Decoder) DecodeInt8() (int8, error) { + n, err := d.DecodeInt64() + return int8(n), err +} + +func (d *Decoder) DecodeInt16() (int16, error) { + n, err := d.DecodeInt64() + return int16(n), err +} + +func (d *Decoder) DecodeInt32() (int32, error) { + n, err := d.DecodeInt64() + return int32(n), err +} + +func decodeFloat32Value(d *Decoder, v reflect.Value) error { + f, err := d.DecodeFloat32() + if err != nil { + return err + } + v.SetFloat(float64(f)) + return nil +} + +func decodeFloat64Value(d *Decoder, v reflect.Value) error { + f, err := d.DecodeFloat64() + if err != nil { + return err + } + v.SetFloat(f) + return nil +} + +func decodeInt64Value(d *Decoder, v reflect.Value) error { + n, err := d.DecodeInt64() + if err != nil { + return err + } + v.SetInt(n) + return nil +} + +func decodeUint64Value(d *Decoder, v reflect.Value) error { + n, err := d.DecodeUint64() + if err != nil { + return err + } + v.SetUint(n) + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_query.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_query.go new file mode 100644 index 00000000..c302ed1f --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_query.go @@ -0,0 +1,158 @@ +package msgpack + +import ( + "fmt" + "strconv" + "strings" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +type queryResult struct { + query string + key string + hasAsterisk bool + + values []interface{} +} + +func (q *queryResult) nextKey() { + ind := strings.IndexByte(q.query, '.') + if ind == -1 { + q.key = q.query + q.query = "" + return + } + q.key = q.query[:ind] + q.query = q.query[ind+1:] +} + +// Query extracts data specified by the query from the msgpack stream skipping +// any other data. Query consists of map keys and array indexes separated with dot, +// e.g. key1.0.key2. +func (d *Decoder) Query(query string) ([]interface{}, error) { + res := queryResult{ + query: query, + } + if err := d.query(&res); err != nil { + return nil, err + } + return res.values, nil +} + +func (d *Decoder) query(q *queryResult) error { + q.nextKey() + if q.key == "" { + v, err := d.decodeInterfaceCond() + if err != nil { + return err + } + q.values = append(q.values, v) + return nil + } + + code, err := d.PeekCode() + if err != nil { + return err + } + + switch { + case code == msgpcode.Map16 || code == msgpcode.Map32 || msgpcode.IsFixedMap(code): + err = d.queryMapKey(q) + case code == msgpcode.Array16 || code == msgpcode.Array32 || msgpcode.IsFixedArray(code): + err = d.queryArrayIndex(q) + default: + err = fmt.Errorf("msgpack: unsupported code=%x decoding key=%q", code, q.key) + } + return err +} + +func (d *Decoder) queryMapKey(q *queryResult) error { + n, err := d.DecodeMapLen() + if err != nil { + return err + } + if n == -1 { + return nil + } + + for i := 0; i < n; i++ { + key, err := d.decodeStringTemp() + if err != nil { + return err + } + + if key == q.key { + if err := d.query(q); err != nil { + return err + } + if q.hasAsterisk { + return d.skipNext((n - i - 1) * 2) + } + return nil + } + + if err := d.Skip(); err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) queryArrayIndex(q *queryResult) error { + n, err := d.DecodeArrayLen() + if err != nil { + return err + } + if n == -1 { + return nil + } + + if q.key == "*" { + q.hasAsterisk = true + + query := q.query + for i := 0; i < n; i++ { + q.query = query + if err := d.query(q); err != nil { + return err + } + } + + q.hasAsterisk = false + return nil + } + + ind, err := strconv.Atoi(q.key) + if err != nil { + return err + } + + for i := 0; i < n; i++ { + if i == ind { + if err := d.query(q); err != nil { + return err + } + if q.hasAsterisk { + return d.skipNext(n - i - 1) + } + return nil + } + + if err := d.Skip(); err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) skipNext(n int) error { + for i := 0; i < n; i++ { + if err := d.Skip(); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_slice.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_slice.go new file mode 100644 index 00000000..db6f7c54 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_slice.go @@ -0,0 +1,191 @@ +package msgpack + +import ( + "fmt" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +var sliceStringPtrType = reflect.TypeOf((*[]string)(nil)) + +// DecodeArrayLen decodes array length. Length is -1 when array is nil. +func (d *Decoder) DecodeArrayLen() (int, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.arrayLen(c) +} + +func (d *Decoder) arrayLen(c byte) (int, error) { + if c == msgpcode.Nil { + return -1, nil + } else if c >= msgpcode.FixedArrayLow && c <= msgpcode.FixedArrayHigh { + return int(c & msgpcode.FixedArrayMask), nil + } + switch c { + case msgpcode.Array16: + n, err := d.uint16() + return int(n), err + case msgpcode.Array32: + n, err := d.uint32() + return int(n), err + } + return 0, fmt.Errorf("msgpack: invalid code=%x decoding array length", c) +} + +func decodeStringSliceValue(d *Decoder, v reflect.Value) error { + ptr := v.Addr().Convert(sliceStringPtrType).Interface().(*[]string) + return d.decodeStringSlicePtr(ptr) +} + +func (d *Decoder) decodeStringSlicePtr(ptr *[]string) error { + n, err := d.DecodeArrayLen() + if err != nil { + return err + } + if n == -1 { + return nil + } + + ss := makeStrings(*ptr, n) + for i := 0; i < n; i++ { + s, err := d.DecodeString() + if err != nil { + return err + } + ss = append(ss, s) + } + *ptr = ss + + return nil +} + +func makeStrings(s []string, n int) []string { + if n > sliceAllocLimit { + n = sliceAllocLimit + } + + if s == nil { + return make([]string, 0, n) + } + + if cap(s) >= n { + return s[:0] + } + + s = s[:cap(s)] + s = append(s, make([]string, n-len(s))...) + return s[:0] +} + +func decodeSliceValue(d *Decoder, v reflect.Value) error { + n, err := d.DecodeArrayLen() + if err != nil { + return err + } + + if n == -1 { + v.Set(reflect.Zero(v.Type())) + return nil + } + if n == 0 && v.IsNil() { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + return nil + } + + if v.Cap() >= n { + v.Set(v.Slice(0, n)) + } else if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Cap())) + } + + for i := 0; i < n; i++ { + if i >= v.Len() { + v.Set(growSliceValue(v, n)) + } + elem := v.Index(i) + if err := d.DecodeValue(elem); err != nil { + return err + } + } + + return nil +} + +func growSliceValue(v reflect.Value, n int) reflect.Value { + diff := n - v.Len() + if diff > sliceAllocLimit { + diff = sliceAllocLimit + } + v = reflect.AppendSlice(v, reflect.MakeSlice(v.Type(), diff, diff)) + return v +} + +func decodeArrayValue(d *Decoder, v reflect.Value) error { + n, err := d.DecodeArrayLen() + if err != nil { + return err + } + + if n == -1 { + return nil + } + if n > v.Len() { + return fmt.Errorf("%s len is %d, but msgpack has %d elements", v.Type(), v.Len(), n) + } + + for i := 0; i < n; i++ { + sv := v.Index(i) + if err := d.DecodeValue(sv); err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) DecodeSlice() ([]interface{}, error) { + c, err := d.readCode() + if err != nil { + return nil, err + } + return d.decodeSlice(c) +} + +func (d *Decoder) decodeSlice(c byte) ([]interface{}, error) { + n, err := d.arrayLen(c) + if err != nil { + return nil, err + } + if n == -1 { + return nil, nil + } + + s := make([]interface{}, 0, min(n, sliceAllocLimit)) + for i := 0; i < n; i++ { + v, err := d.decodeInterfaceCond() + if err != nil { + return nil, err + } + s = append(s, v) + } + + return s, nil +} + +func (d *Decoder) skipSlice(c byte) error { + n, err := d.arrayLen(c) + if err != nil { + return err + } + + for i := 0; i < n; i++ { + if err := d.Skip(); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_string.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_string.go new file mode 100644 index 00000000..e837e08b --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_string.go @@ -0,0 +1,192 @@ +package msgpack + +import ( + "fmt" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +func (d *Decoder) bytesLen(c byte) (int, error) { + if c == msgpcode.Nil { + return -1, nil + } + + if msgpcode.IsFixedString(c) { + return int(c & msgpcode.FixedStrMask), nil + } + + switch c { + case msgpcode.Str8, msgpcode.Bin8: + n, err := d.uint8() + return int(n), err + case msgpcode.Str16, msgpcode.Bin16: + n, err := d.uint16() + return int(n), err + case msgpcode.Str32, msgpcode.Bin32: + n, err := d.uint32() + return int(n), err + } + + return 0, fmt.Errorf("msgpack: invalid code=%x decoding string/bytes length", c) +} + +func (d *Decoder) DecodeString() (string, error) { + if intern := d.flags&useInternedStringsFlag != 0; intern || len(d.dict) > 0 { + return d.decodeInternedString(intern) + } + + c, err := d.readCode() + if err != nil { + return "", err + } + return d.string(c) +} + +func (d *Decoder) string(c byte) (string, error) { + n, err := d.bytesLen(c) + if err != nil { + return "", err + } + return d.stringWithLen(n) +} + +func (d *Decoder) stringWithLen(n int) (string, error) { + if n <= 0 { + return "", nil + } + b, err := d.readN(n) + return string(b), err +} + +func decodeStringValue(d *Decoder, v reflect.Value) error { + s, err := d.DecodeString() + if err != nil { + return err + } + v.SetString(s) + return nil +} + +func (d *Decoder) DecodeBytesLen() (int, error) { + c, err := d.readCode() + if err != nil { + return 0, err + } + return d.bytesLen(c) +} + +func (d *Decoder) DecodeBytes() ([]byte, error) { + c, err := d.readCode() + if err != nil { + return nil, err + } + return d.bytes(c, nil) +} + +func (d *Decoder) bytes(c byte, b []byte) ([]byte, error) { + n, err := d.bytesLen(c) + if err != nil { + return nil, err + } + if n == -1 { + return nil, nil + } + return readN(d.r, b, n) +} + +func (d *Decoder) decodeStringTemp() (string, error) { + if intern := d.flags&useInternedStringsFlag != 0; intern || len(d.dict) > 0 { + return d.decodeInternedString(intern) + } + + c, err := d.readCode() + if err != nil { + return "", err + } + + n, err := d.bytesLen(c) + if err != nil { + return "", err + } + if n == -1 { + return "", nil + } + + b, err := d.readN(n) + if err != nil { + return "", err + } + + return bytesToString(b), nil +} + +func (d *Decoder) decodeBytesPtr(ptr *[]byte) error { + c, err := d.readCode() + if err != nil { + return err + } + return d.bytesPtr(c, ptr) +} + +func (d *Decoder) bytesPtr(c byte, ptr *[]byte) error { + n, err := d.bytesLen(c) + if err != nil { + return err + } + if n == -1 { + *ptr = nil + return nil + } + + *ptr, err = readN(d.r, *ptr, n) + return err +} + +func (d *Decoder) skipBytes(c byte) error { + n, err := d.bytesLen(c) + if err != nil { + return err + } + if n <= 0 { + return nil + } + return d.skipN(n) +} + +func decodeBytesValue(d *Decoder, v reflect.Value) error { + c, err := d.readCode() + if err != nil { + return err + } + + b, err := d.bytes(c, v.Bytes()) + if err != nil { + return err + } + + v.SetBytes(b) + + return nil +} + +func decodeByteArrayValue(d *Decoder, v reflect.Value) error { + c, err := d.readCode() + if err != nil { + return err + } + + n, err := d.bytesLen(c) + if err != nil { + return err + } + if n == -1 { + return nil + } + if n > v.Len() { + return fmt.Errorf("%s len is %d, but msgpack has %d elements", v.Type(), v.Len(), n) + } + + b := v.Slice(0, n).Bytes() + return d.readFull(b) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/decode_value.go b/vendor/github.com/vmihailenco/msgpack/v5/decode_value.go new file mode 100644 index 00000000..d2ff2aea --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/decode_value.go @@ -0,0 +1,250 @@ +package msgpack + +import ( + "encoding" + "errors" + "fmt" + "reflect" +) + +var ( + interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() + stringType = reflect.TypeOf((*string)(nil)).Elem() +) + +var valueDecoders []decoderFunc + +//nolint:gochecknoinits +func init() { + valueDecoders = []decoderFunc{ + reflect.Bool: decodeBoolValue, + reflect.Int: decodeInt64Value, + reflect.Int8: decodeInt64Value, + reflect.Int16: decodeInt64Value, + reflect.Int32: decodeInt64Value, + reflect.Int64: decodeInt64Value, + reflect.Uint: decodeUint64Value, + reflect.Uint8: decodeUint64Value, + reflect.Uint16: decodeUint64Value, + reflect.Uint32: decodeUint64Value, + reflect.Uint64: decodeUint64Value, + reflect.Float32: decodeFloat32Value, + reflect.Float64: decodeFloat64Value, + reflect.Complex64: decodeUnsupportedValue, + reflect.Complex128: decodeUnsupportedValue, + reflect.Array: decodeArrayValue, + reflect.Chan: decodeUnsupportedValue, + reflect.Func: decodeUnsupportedValue, + reflect.Interface: decodeInterfaceValue, + reflect.Map: decodeMapValue, + reflect.Ptr: decodeUnsupportedValue, + reflect.Slice: decodeSliceValue, + reflect.String: decodeStringValue, + reflect.Struct: decodeStructValue, + reflect.UnsafePointer: decodeUnsupportedValue, + } +} + +func getDecoder(typ reflect.Type) decoderFunc { + if v, ok := typeDecMap.Load(typ); ok { + return v.(decoderFunc) + } + fn := _getDecoder(typ) + typeDecMap.Store(typ, fn) + return fn +} + +func _getDecoder(typ reflect.Type) decoderFunc { + kind := typ.Kind() + + if kind == reflect.Ptr { + if _, ok := typeDecMap.Load(typ.Elem()); ok { + return ptrValueDecoder(typ) + } + } + + if typ.Implements(customDecoderType) { + return nilAwareDecoder(typ, decodeCustomValue) + } + if typ.Implements(unmarshalerType) { + return nilAwareDecoder(typ, unmarshalValue) + } + if typ.Implements(binaryUnmarshalerType) { + return nilAwareDecoder(typ, unmarshalBinaryValue) + } + if typ.Implements(textUnmarshalerType) { + return nilAwareDecoder(typ, unmarshalTextValue) + } + + // Addressable struct field value. + if kind != reflect.Ptr { + ptr := reflect.PtrTo(typ) + if ptr.Implements(customDecoderType) { + return addrDecoder(nilAwareDecoder(typ, decodeCustomValue)) + } + if ptr.Implements(unmarshalerType) { + return addrDecoder(nilAwareDecoder(typ, unmarshalValue)) + } + if ptr.Implements(binaryUnmarshalerType) { + return addrDecoder(nilAwareDecoder(typ, unmarshalBinaryValue)) + } + if ptr.Implements(textUnmarshalerType) { + return addrDecoder(nilAwareDecoder(typ, unmarshalTextValue)) + } + } + + switch kind { + case reflect.Ptr: + return ptrValueDecoder(typ) + case reflect.Slice: + elem := typ.Elem() + if elem.Kind() == reflect.Uint8 { + return decodeBytesValue + } + if elem == stringType { + return decodeStringSliceValue + } + case reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + return decodeByteArrayValue + } + case reflect.Map: + if typ.Key() == stringType { + switch typ.Elem() { + case stringType: + return decodeMapStringStringValue + case interfaceType: + return decodeMapStringInterfaceValue + } + } + } + + return valueDecoders[kind] +} + +func ptrValueDecoder(typ reflect.Type) decoderFunc { + decoder := getDecoder(typ.Elem()) + return func(d *Decoder, v reflect.Value) error { + if d.hasNilCode() { + if !v.IsNil() { + v.Set(reflect.Zero(v.Type())) + } + return d.DecodeNil() + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return decoder(d, v.Elem()) + } +} + +func addrDecoder(fn decoderFunc) decoderFunc { + return func(d *Decoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface()) + } + return fn(d, v.Addr()) + } +} + +func nilAwareDecoder(typ reflect.Type, fn decoderFunc) decoderFunc { + if nilable(typ.Kind()) { + return func(d *Decoder, v reflect.Value) error { + if d.hasNilCode() { + return d.decodeNilValue(v) + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return fn(d, v) + } + } + + return func(d *Decoder, v reflect.Value) error { + if d.hasNilCode() { + return d.decodeNilValue(v) + } + return fn(d, v) + } +} + +func decodeBoolValue(d *Decoder, v reflect.Value) error { + flag, err := d.DecodeBool() + if err != nil { + return err + } + v.SetBool(flag) + return nil +} + +func decodeInterfaceValue(d *Decoder, v reflect.Value) error { + if v.IsNil() { + return d.interfaceValue(v) + } + return d.DecodeValue(v.Elem()) +} + +func (d *Decoder) interfaceValue(v reflect.Value) error { + vv, err := d.decodeInterfaceCond() + if err != nil { + return err + } + + if vv != nil { + if v.Type() == errorType { + if vv, ok := vv.(string); ok { + v.Set(reflect.ValueOf(errors.New(vv))) + return nil + } + } + + v.Set(reflect.ValueOf(vv)) + } + + return nil +} + +func decodeUnsupportedValue(d *Decoder, v reflect.Value) error { + return fmt.Errorf("msgpack: Decode(unsupported %s)", v.Type()) +} + +//------------------------------------------------------------------------------ + +func decodeCustomValue(d *Decoder, v reflect.Value) error { + decoder := v.Interface().(CustomDecoder) + return decoder.DecodeMsgpack(d) +} + +func unmarshalValue(d *Decoder, v reflect.Value) error { + var b []byte + + d.rec = make([]byte, 0, 64) + if err := d.Skip(); err != nil { + return err + } + b = d.rec + d.rec = nil + + unmarshaler := v.Interface().(Unmarshaler) + return unmarshaler.UnmarshalMsgpack(b) +} + +func unmarshalBinaryValue(d *Decoder, v reflect.Value) error { + data, err := d.DecodeBytes() + if err != nil { + return err + } + + unmarshaler := v.Interface().(encoding.BinaryUnmarshaler) + return unmarshaler.UnmarshalBinary(data) +} + +func unmarshalTextValue(d *Decoder, v reflect.Value) error { + data, err := d.DecodeBytes() + if err != nil { + return err + } + + unmarshaler := v.Interface().(encoding.TextUnmarshaler) + return unmarshaler.UnmarshalText(data) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/encode.go b/vendor/github.com/vmihailenco/msgpack/v5/encode.go new file mode 100644 index 00000000..0ef6212e --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/encode.go @@ -0,0 +1,269 @@ +package msgpack + +import ( + "bytes" + "io" + "reflect" + "sync" + "time" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +const ( + sortMapKeysFlag uint32 = 1 << iota + arrayEncodedStructsFlag + useCompactIntsFlag + useCompactFloatsFlag + useInternedStringsFlag + omitEmptyFlag +) + +type writer interface { + io.Writer + WriteByte(byte) error +} + +type byteWriter struct { + io.Writer +} + +func newByteWriter(w io.Writer) byteWriter { + return byteWriter{ + Writer: w, + } +} + +func (bw byteWriter) WriteByte(c byte) error { + _, err := bw.Write([]byte{c}) + return err +} + +//------------------------------------------------------------------------------ + +var encPool = sync.Pool{ + New: func() interface{} { + return NewEncoder(nil) + }, +} + +func GetEncoder() *Encoder { + return encPool.Get().(*Encoder) +} + +func PutEncoder(enc *Encoder) { + enc.w = nil + encPool.Put(enc) +} + +// Marshal returns the MessagePack encoding of v. +func Marshal(v interface{}) ([]byte, error) { + enc := GetEncoder() + + var buf bytes.Buffer + enc.Reset(&buf) + + err := enc.Encode(v) + b := buf.Bytes() + + PutEncoder(enc) + + if err != nil { + return nil, err + } + return b, err +} + +type Encoder struct { + w writer + + buf []byte + timeBuf []byte + + dict map[string]int + + flags uint32 + structTag string +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + e := &Encoder{ + buf: make([]byte, 9), + } + e.Reset(w) + return e +} + +// Writer returns the Encoder's writer. +func (e *Encoder) Writer() io.Writer { + return e.w +} + +// Reset discards any buffered data, resets all state, and switches the writer to write to w. +func (e *Encoder) Reset(w io.Writer) { + e.ResetDict(w, nil) +} + +// ResetDict is like Reset, but also resets the dict. +func (e *Encoder) ResetDict(w io.Writer, dict map[string]int) { + e.resetWriter(w) + e.flags = 0 + e.structTag = "" + e.dict = dict +} + +func (e *Encoder) WithDict(dict map[string]int, fn func(*Encoder) error) error { + oldDict := e.dict + e.dict = dict + err := fn(e) + e.dict = oldDict + return err +} + +func (e *Encoder) resetWriter(w io.Writer) { + if bw, ok := w.(writer); ok { + e.w = bw + } else { + e.w = newByteWriter(w) + } +} + +// SetSortMapKeys causes the Encoder to encode map keys in increasing order. +// Supported map types are: +// - map[string]string +// - map[string]interface{} +func (e *Encoder) SetSortMapKeys(on bool) *Encoder { + if on { + e.flags |= sortMapKeysFlag + } else { + e.flags &= ^sortMapKeysFlag + } + return e +} + +// SetCustomStructTag causes the Encoder to use a custom struct tag as +// fallback option if there is no msgpack tag. +func (e *Encoder) SetCustomStructTag(tag string) { + e.structTag = tag +} + +// SetOmitEmpty causes the Encoder to omit empty values by default. +func (e *Encoder) SetOmitEmpty(on bool) { + if on { + e.flags |= omitEmptyFlag + } else { + e.flags &= ^omitEmptyFlag + } +} + +// UseArrayEncodedStructs causes the Encoder to encode Go structs as msgpack arrays. +func (e *Encoder) UseArrayEncodedStructs(on bool) { + if on { + e.flags |= arrayEncodedStructsFlag + } else { + e.flags &= ^arrayEncodedStructsFlag + } +} + +// UseCompactEncoding causes the Encoder to chose the most compact encoding. +// For example, it allows to encode small Go int64 as msgpack int8 saving 7 bytes. +func (e *Encoder) UseCompactInts(on bool) { + if on { + e.flags |= useCompactIntsFlag + } else { + e.flags &= ^useCompactIntsFlag + } +} + +// UseCompactFloats causes the Encoder to chose a compact integer encoding +// for floats that can be represented as integers. +func (e *Encoder) UseCompactFloats(on bool) { + if on { + e.flags |= useCompactFloatsFlag + } else { + e.flags &= ^useCompactFloatsFlag + } +} + +// UseInternedStrings causes the Encoder to intern strings. +func (e *Encoder) UseInternedStrings(on bool) { + if on { + e.flags |= useInternedStringsFlag + } else { + e.flags &= ^useInternedStringsFlag + } +} + +func (e *Encoder) Encode(v interface{}) error { + switch v := v.(type) { + case nil: + return e.EncodeNil() + case string: + return e.EncodeString(v) + case []byte: + return e.EncodeBytes(v) + case int: + return e.EncodeInt(int64(v)) + case int64: + return e.encodeInt64Cond(v) + case uint: + return e.EncodeUint(uint64(v)) + case uint64: + return e.encodeUint64Cond(v) + case bool: + return e.EncodeBool(v) + case float32: + return e.EncodeFloat32(v) + case float64: + return e.EncodeFloat64(v) + case time.Duration: + return e.encodeInt64Cond(int64(v)) + case time.Time: + return e.EncodeTime(v) + } + return e.EncodeValue(reflect.ValueOf(v)) +} + +func (e *Encoder) EncodeMulti(v ...interface{}) error { + for _, vv := range v { + if err := e.Encode(vv); err != nil { + return err + } + } + return nil +} + +func (e *Encoder) EncodeValue(v reflect.Value) error { + fn := getEncoder(v.Type()) + return fn(e, v) +} + +func (e *Encoder) EncodeNil() error { + return e.writeCode(msgpcode.Nil) +} + +func (e *Encoder) EncodeBool(value bool) error { + if value { + return e.writeCode(msgpcode.True) + } + return e.writeCode(msgpcode.False) +} + +func (e *Encoder) EncodeDuration(d time.Duration) error { + return e.EncodeInt(int64(d)) +} + +func (e *Encoder) writeCode(c byte) error { + return e.w.WriteByte(c) +} + +func (e *Encoder) write(b []byte) error { + _, err := e.w.Write(b) + return err +} + +func (e *Encoder) writeString(s string) error { + _, err := e.w.Write(stringToBytes(s)) + return err +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/encode_map.go b/vendor/github.com/vmihailenco/msgpack/v5/encode_map.go new file mode 100644 index 00000000..ba4c61be --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/encode_map.go @@ -0,0 +1,179 @@ +package msgpack + +import ( + "math" + "reflect" + "sort" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +func encodeMapValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + + if err := e.EncodeMapLen(v.Len()); err != nil { + return err + } + + iter := v.MapRange() + for iter.Next() { + if err := e.EncodeValue(iter.Key()); err != nil { + return err + } + if err := e.EncodeValue(iter.Value()); err != nil { + return err + } + } + + return nil +} + +func encodeMapStringStringValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + + if err := e.EncodeMapLen(v.Len()); err != nil { + return err + } + + m := v.Convert(mapStringStringType).Interface().(map[string]string) + if e.flags&sortMapKeysFlag != 0 { + return e.encodeSortedMapStringString(m) + } + + for mk, mv := range m { + if err := e.EncodeString(mk); err != nil { + return err + } + if err := e.EncodeString(mv); err != nil { + return err + } + } + + return nil +} + +func encodeMapStringInterfaceValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + m := v.Convert(mapStringInterfaceType).Interface().(map[string]interface{}) + if e.flags&sortMapKeysFlag != 0 { + return e.EncodeMapSorted(m) + } + return e.EncodeMap(m) +} + +func (e *Encoder) EncodeMap(m map[string]interface{}) error { + if m == nil { + return e.EncodeNil() + } + if err := e.EncodeMapLen(len(m)); err != nil { + return err + } + for mk, mv := range m { + if err := e.EncodeString(mk); err != nil { + return err + } + if err := e.Encode(mv); err != nil { + return err + } + } + return nil +} + +func (e *Encoder) EncodeMapSorted(m map[string]interface{}) error { + if m == nil { + return e.EncodeNil() + } + if err := e.EncodeMapLen(len(m)); err != nil { + return err + } + + keys := make([]string, 0, len(m)) + + for k := range m { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, k := range keys { + if err := e.EncodeString(k); err != nil { + return err + } + if err := e.Encode(m[k]); err != nil { + return err + } + } + + return nil +} + +func (e *Encoder) encodeSortedMapStringString(m map[string]string) error { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + err := e.EncodeString(k) + if err != nil { + return err + } + if err = e.EncodeString(m[k]); err != nil { + return err + } + } + + return nil +} + +func (e *Encoder) EncodeMapLen(l int) error { + if l < 16 { + return e.writeCode(msgpcode.FixedMapLow | byte(l)) + } + if l <= math.MaxUint16 { + return e.write2(msgpcode.Map16, uint16(l)) + } + return e.write4(msgpcode.Map32, uint32(l)) +} + +func encodeStructValue(e *Encoder, strct reflect.Value) error { + structFields := structs.Fields(strct.Type(), e.structTag) + if e.flags&arrayEncodedStructsFlag != 0 || structFields.AsArray { + return encodeStructValueAsArray(e, strct, structFields.List) + } + fields := structFields.OmitEmpty(strct, e.flags&omitEmptyFlag != 0) + + if err := e.EncodeMapLen(len(fields)); err != nil { + return err + } + + for _, f := range fields { + if err := e.EncodeString(f.name); err != nil { + return err + } + if err := f.EncodeValue(e, strct); err != nil { + return err + } + } + + return nil +} + +func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error { + if err := e.EncodeArrayLen(len(fields)); err != nil { + return err + } + for _, f := range fields { + if err := f.EncodeValue(e, strct); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/encode_number.go b/vendor/github.com/vmihailenco/msgpack/v5/encode_number.go new file mode 100644 index 00000000..63c311bf --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/encode_number.go @@ -0,0 +1,252 @@ +package msgpack + +import ( + "math" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +// EncodeUint8 encodes an uint8 in 2 bytes preserving type of the number. +func (e *Encoder) EncodeUint8(n uint8) error { + return e.write1(msgpcode.Uint8, n) +} + +func (e *Encoder) encodeUint8Cond(n uint8) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeUint(uint64(n)) + } + return e.EncodeUint8(n) +} + +// EncodeUint16 encodes an uint16 in 3 bytes preserving type of the number. +func (e *Encoder) EncodeUint16(n uint16) error { + return e.write2(msgpcode.Uint16, n) +} + +func (e *Encoder) encodeUint16Cond(n uint16) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeUint(uint64(n)) + } + return e.EncodeUint16(n) +} + +// EncodeUint32 encodes an uint16 in 5 bytes preserving type of the number. +func (e *Encoder) EncodeUint32(n uint32) error { + return e.write4(msgpcode.Uint32, n) +} + +func (e *Encoder) encodeUint32Cond(n uint32) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeUint(uint64(n)) + } + return e.EncodeUint32(n) +} + +// EncodeUint64 encodes an uint16 in 9 bytes preserving type of the number. +func (e *Encoder) EncodeUint64(n uint64) error { + return e.write8(msgpcode.Uint64, n) +} + +func (e *Encoder) encodeUint64Cond(n uint64) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeUint(n) + } + return e.EncodeUint64(n) +} + +// EncodeInt8 encodes an int8 in 2 bytes preserving type of the number. +func (e *Encoder) EncodeInt8(n int8) error { + return e.write1(msgpcode.Int8, uint8(n)) +} + +func (e *Encoder) encodeInt8Cond(n int8) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeInt(int64(n)) + } + return e.EncodeInt8(n) +} + +// EncodeInt16 encodes an int16 in 3 bytes preserving type of the number. +func (e *Encoder) EncodeInt16(n int16) error { + return e.write2(msgpcode.Int16, uint16(n)) +} + +func (e *Encoder) encodeInt16Cond(n int16) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeInt(int64(n)) + } + return e.EncodeInt16(n) +} + +// EncodeInt32 encodes an int32 in 5 bytes preserving type of the number. +func (e *Encoder) EncodeInt32(n int32) error { + return e.write4(msgpcode.Int32, uint32(n)) +} + +func (e *Encoder) encodeInt32Cond(n int32) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeInt(int64(n)) + } + return e.EncodeInt32(n) +} + +// EncodeInt64 encodes an int64 in 9 bytes preserving type of the number. +func (e *Encoder) EncodeInt64(n int64) error { + return e.write8(msgpcode.Int64, uint64(n)) +} + +func (e *Encoder) encodeInt64Cond(n int64) error { + if e.flags&useCompactIntsFlag != 0 { + return e.EncodeInt(n) + } + return e.EncodeInt64(n) +} + +// EncodeUnsignedNumber encodes an uint64 in 1, 2, 3, 5, or 9 bytes. +// Type of the number is lost during encoding. +func (e *Encoder) EncodeUint(n uint64) error { + if n <= math.MaxInt8 { + return e.w.WriteByte(byte(n)) + } + if n <= math.MaxUint8 { + return e.EncodeUint8(uint8(n)) + } + if n <= math.MaxUint16 { + return e.EncodeUint16(uint16(n)) + } + if n <= math.MaxUint32 { + return e.EncodeUint32(uint32(n)) + } + return e.EncodeUint64(n) +} + +// EncodeNumber encodes an int64 in 1, 2, 3, 5, or 9 bytes. +// Type of the number is lost during encoding. +func (e *Encoder) EncodeInt(n int64) error { + if n >= 0 { + return e.EncodeUint(uint64(n)) + } + if n >= int64(int8(msgpcode.NegFixedNumLow)) { + return e.w.WriteByte(byte(n)) + } + if n >= math.MinInt8 { + return e.EncodeInt8(int8(n)) + } + if n >= math.MinInt16 { + return e.EncodeInt16(int16(n)) + } + if n >= math.MinInt32 { + return e.EncodeInt32(int32(n)) + } + return e.EncodeInt64(n) +} + +func (e *Encoder) EncodeFloat32(n float32) error { + if e.flags&useCompactFloatsFlag != 0 { + if float32(int64(n)) == n { + return e.EncodeInt(int64(n)) + } + } + return e.write4(msgpcode.Float, math.Float32bits(n)) +} + +func (e *Encoder) EncodeFloat64(n float64) error { + if e.flags&useCompactFloatsFlag != 0 { + // Both NaN and Inf convert to int64(-0x8000000000000000) + // If n is NaN then it never compares true with any other value + // If n is Inf then it doesn't convert from int64 back to +/-Inf + // In both cases the comparison works. + if float64(int64(n)) == n { + return e.EncodeInt(int64(n)) + } + } + return e.write8(msgpcode.Double, math.Float64bits(n)) +} + +func (e *Encoder) write1(code byte, n uint8) error { + e.buf = e.buf[:2] + e.buf[0] = code + e.buf[1] = n + return e.write(e.buf) +} + +func (e *Encoder) write2(code byte, n uint16) error { + e.buf = e.buf[:3] + e.buf[0] = code + e.buf[1] = byte(n >> 8) + e.buf[2] = byte(n) + return e.write(e.buf) +} + +func (e *Encoder) write4(code byte, n uint32) error { + e.buf = e.buf[:5] + e.buf[0] = code + e.buf[1] = byte(n >> 24) + e.buf[2] = byte(n >> 16) + e.buf[3] = byte(n >> 8) + e.buf[4] = byte(n) + return e.write(e.buf) +} + +func (e *Encoder) write8(code byte, n uint64) error { + e.buf = e.buf[:9] + e.buf[0] = code + e.buf[1] = byte(n >> 56) + e.buf[2] = byte(n >> 48) + e.buf[3] = byte(n >> 40) + e.buf[4] = byte(n >> 32) + e.buf[5] = byte(n >> 24) + e.buf[6] = byte(n >> 16) + e.buf[7] = byte(n >> 8) + e.buf[8] = byte(n) + return e.write(e.buf) +} + +func encodeUintValue(e *Encoder, v reflect.Value) error { + return e.EncodeUint(v.Uint()) +} + +func encodeIntValue(e *Encoder, v reflect.Value) error { + return e.EncodeInt(v.Int()) +} + +func encodeUint8CondValue(e *Encoder, v reflect.Value) error { + return e.encodeUint8Cond(uint8(v.Uint())) +} + +func encodeUint16CondValue(e *Encoder, v reflect.Value) error { + return e.encodeUint16Cond(uint16(v.Uint())) +} + +func encodeUint32CondValue(e *Encoder, v reflect.Value) error { + return e.encodeUint32Cond(uint32(v.Uint())) +} + +func encodeUint64CondValue(e *Encoder, v reflect.Value) error { + return e.encodeUint64Cond(v.Uint()) +} + +func encodeInt8CondValue(e *Encoder, v reflect.Value) error { + return e.encodeInt8Cond(int8(v.Int())) +} + +func encodeInt16CondValue(e *Encoder, v reflect.Value) error { + return e.encodeInt16Cond(int16(v.Int())) +} + +func encodeInt32CondValue(e *Encoder, v reflect.Value) error { + return e.encodeInt32Cond(int32(v.Int())) +} + +func encodeInt64CondValue(e *Encoder, v reflect.Value) error { + return e.encodeInt64Cond(v.Int()) +} + +func encodeFloat32Value(e *Encoder, v reflect.Value) error { + return e.EncodeFloat32(float32(v.Float())) +} + +func encodeFloat64Value(e *Encoder, v reflect.Value) error { + return e.EncodeFloat64(v.Float()) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/encode_slice.go b/vendor/github.com/vmihailenco/msgpack/v5/encode_slice.go new file mode 100644 index 00000000..ca46eada --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/encode_slice.go @@ -0,0 +1,139 @@ +package msgpack + +import ( + "math" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +var stringSliceType = reflect.TypeOf(([]string)(nil)) + +func encodeStringValue(e *Encoder, v reflect.Value) error { + return e.EncodeString(v.String()) +} + +func encodeByteSliceValue(e *Encoder, v reflect.Value) error { + return e.EncodeBytes(v.Bytes()) +} + +func encodeByteArrayValue(e *Encoder, v reflect.Value) error { + if err := e.EncodeBytesLen(v.Len()); err != nil { + return err + } + + if v.CanAddr() { + b := v.Slice(0, v.Len()).Bytes() + return e.write(b) + } + + e.buf = grow(e.buf, v.Len()) + reflect.Copy(reflect.ValueOf(e.buf), v) + return e.write(e.buf) +} + +func grow(b []byte, n int) []byte { + if cap(b) >= n { + return b[:n] + } + b = b[:cap(b)] + b = append(b, make([]byte, n-len(b))...) + return b +} + +func (e *Encoder) EncodeBytesLen(l int) error { + if l < 256 { + return e.write1(msgpcode.Bin8, uint8(l)) + } + if l <= math.MaxUint16 { + return e.write2(msgpcode.Bin16, uint16(l)) + } + return e.write4(msgpcode.Bin32, uint32(l)) +} + +func (e *Encoder) encodeStringLen(l int) error { + if l < 32 { + return e.writeCode(msgpcode.FixedStrLow | byte(l)) + } + if l < 256 { + return e.write1(msgpcode.Str8, uint8(l)) + } + if l <= math.MaxUint16 { + return e.write2(msgpcode.Str16, uint16(l)) + } + return e.write4(msgpcode.Str32, uint32(l)) +} + +func (e *Encoder) EncodeString(v string) error { + if intern := e.flags&useInternedStringsFlag != 0; intern || len(e.dict) > 0 { + return e.encodeInternedString(v, intern) + } + return e.encodeNormalString(v) +} + +func (e *Encoder) encodeNormalString(v string) error { + if err := e.encodeStringLen(len(v)); err != nil { + return err + } + return e.writeString(v) +} + +func (e *Encoder) EncodeBytes(v []byte) error { + if v == nil { + return e.EncodeNil() + } + if err := e.EncodeBytesLen(len(v)); err != nil { + return err + } + return e.write(v) +} + +func (e *Encoder) EncodeArrayLen(l int) error { + if l < 16 { + return e.writeCode(msgpcode.FixedArrayLow | byte(l)) + } + if l <= math.MaxUint16 { + return e.write2(msgpcode.Array16, uint16(l)) + } + return e.write4(msgpcode.Array32, uint32(l)) +} + +func encodeStringSliceValue(e *Encoder, v reflect.Value) error { + ss := v.Convert(stringSliceType).Interface().([]string) + return e.encodeStringSlice(ss) +} + +func (e *Encoder) encodeStringSlice(s []string) error { + if s == nil { + return e.EncodeNil() + } + if err := e.EncodeArrayLen(len(s)); err != nil { + return err + } + for _, v := range s { + if err := e.EncodeString(v); err != nil { + return err + } + } + return nil +} + +func encodeSliceValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + return encodeArrayValue(e, v) +} + +func encodeArrayValue(e *Encoder, v reflect.Value) error { + l := v.Len() + if err := e.EncodeArrayLen(l); err != nil { + return err + } + for i := 0; i < l; i++ { + if err := e.EncodeValue(v.Index(i)); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/encode_value.go b/vendor/github.com/vmihailenco/msgpack/v5/encode_value.go new file mode 100644 index 00000000..48cf489f --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/encode_value.go @@ -0,0 +1,245 @@ +package msgpack + +import ( + "encoding" + "fmt" + "reflect" +) + +var valueEncoders []encoderFunc + +//nolint:gochecknoinits +func init() { + valueEncoders = []encoderFunc{ + reflect.Bool: encodeBoolValue, + reflect.Int: encodeIntValue, + reflect.Int8: encodeInt8CondValue, + reflect.Int16: encodeInt16CondValue, + reflect.Int32: encodeInt32CondValue, + reflect.Int64: encodeInt64CondValue, + reflect.Uint: encodeUintValue, + reflect.Uint8: encodeUint8CondValue, + reflect.Uint16: encodeUint16CondValue, + reflect.Uint32: encodeUint32CondValue, + reflect.Uint64: encodeUint64CondValue, + reflect.Float32: encodeFloat32Value, + reflect.Float64: encodeFloat64Value, + reflect.Complex64: encodeUnsupportedValue, + reflect.Complex128: encodeUnsupportedValue, + reflect.Array: encodeArrayValue, + reflect.Chan: encodeUnsupportedValue, + reflect.Func: encodeUnsupportedValue, + reflect.Interface: encodeInterfaceValue, + reflect.Map: encodeMapValue, + reflect.Ptr: encodeUnsupportedValue, + reflect.Slice: encodeSliceValue, + reflect.String: encodeStringValue, + reflect.Struct: encodeStructValue, + reflect.UnsafePointer: encodeUnsupportedValue, + } +} + +func getEncoder(typ reflect.Type) encoderFunc { + if v, ok := typeEncMap.Load(typ); ok { + return v.(encoderFunc) + } + fn := _getEncoder(typ) + typeEncMap.Store(typ, fn) + return fn +} + +func _getEncoder(typ reflect.Type) encoderFunc { + kind := typ.Kind() + + if kind == reflect.Ptr { + if _, ok := typeEncMap.Load(typ.Elem()); ok { + return ptrEncoderFunc(typ) + } + } + + if typ.Implements(customEncoderType) { + return encodeCustomValue + } + if typ.Implements(marshalerType) { + return marshalValue + } + if typ.Implements(binaryMarshalerType) { + return marshalBinaryValue + } + if typ.Implements(textMarshalerType) { + return marshalTextValue + } + + // Addressable struct field value. + if kind != reflect.Ptr { + ptr := reflect.PtrTo(typ) + if ptr.Implements(customEncoderType) { + return encodeCustomValuePtr + } + if ptr.Implements(marshalerType) { + return marshalValuePtr + } + if ptr.Implements(binaryMarshalerType) { + return marshalBinaryValueAddr + } + if ptr.Implements(textMarshalerType) { + return marshalTextValueAddr + } + } + + if typ == errorType { + return encodeErrorValue + } + + switch kind { + case reflect.Ptr: + return ptrEncoderFunc(typ) + case reflect.Slice: + elem := typ.Elem() + if elem.Kind() == reflect.Uint8 { + return encodeByteSliceValue + } + if elem == stringType { + return encodeStringSliceValue + } + case reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + return encodeByteArrayValue + } + case reflect.Map: + if typ.Key() == stringType { + switch typ.Elem() { + case stringType: + return encodeMapStringStringValue + case interfaceType: + return encodeMapStringInterfaceValue + } + } + } + + return valueEncoders[kind] +} + +func ptrEncoderFunc(typ reflect.Type) encoderFunc { + encoder := getEncoder(typ.Elem()) + return func(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + return encoder(e, v.Elem()) + } +} + +func encodeCustomValuePtr(e *Encoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface()) + } + encoder := v.Addr().Interface().(CustomEncoder) + return encoder.EncodeMsgpack(e) +} + +func encodeCustomValue(e *Encoder, v reflect.Value) error { + if nilable(v.Kind()) && v.IsNil() { + return e.EncodeNil() + } + + encoder := v.Interface().(CustomEncoder) + return encoder.EncodeMsgpack(e) +} + +func marshalValuePtr(e *Encoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface()) + } + return marshalValue(e, v.Addr()) +} + +func marshalValue(e *Encoder, v reflect.Value) error { + if nilable(v.Kind()) && v.IsNil() { + return e.EncodeNil() + } + + marshaler := v.Interface().(Marshaler) + b, err := marshaler.MarshalMsgpack() + if err != nil { + return err + } + _, err = e.w.Write(b) + return err +} + +func encodeBoolValue(e *Encoder, v reflect.Value) error { + return e.EncodeBool(v.Bool()) +} + +func encodeInterfaceValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + return e.EncodeValue(v.Elem()) +} + +func encodeErrorValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + return e.EncodeString(v.Interface().(error).Error()) +} + +func encodeUnsupportedValue(e *Encoder, v reflect.Value) error { + return fmt.Errorf("msgpack: Encode(unsupported %s)", v.Type()) +} + +func nilable(kind reflect.Kind) bool { + switch kind { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return true + } + return false +} + +//------------------------------------------------------------------------------ + +func marshalBinaryValueAddr(e *Encoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface()) + } + return marshalBinaryValue(e, v.Addr()) +} + +func marshalBinaryValue(e *Encoder, v reflect.Value) error { + if nilable(v.Kind()) && v.IsNil() { + return e.EncodeNil() + } + + marshaler := v.Interface().(encoding.BinaryMarshaler) + data, err := marshaler.MarshalBinary() + if err != nil { + return err + } + + return e.EncodeBytes(data) +} + +//------------------------------------------------------------------------------ + +func marshalTextValueAddr(e *Encoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface()) + } + return marshalTextValue(e, v.Addr()) +} + +func marshalTextValue(e *Encoder, v reflect.Value) error { + if nilable(v.Kind()) && v.IsNil() { + return e.EncodeNil() + } + + marshaler := v.Interface().(encoding.TextMarshaler) + data, err := marshaler.MarshalText() + if err != nil { + return err + } + + return e.EncodeBytes(data) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/ext.go b/vendor/github.com/vmihailenco/msgpack/v5/ext.go new file mode 100644 index 00000000..76e11603 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/ext.go @@ -0,0 +1,303 @@ +package msgpack + +import ( + "fmt" + "math" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +type extInfo struct { + Type reflect.Type + Decoder func(d *Decoder, v reflect.Value, extLen int) error +} + +var extTypes = make(map[int8]*extInfo) + +type MarshalerUnmarshaler interface { + Marshaler + Unmarshaler +} + +func RegisterExt(extID int8, value MarshalerUnmarshaler) { + RegisterExtEncoder(extID, value, func(e *Encoder, v reflect.Value) ([]byte, error) { + marshaler := v.Interface().(Marshaler) + return marshaler.MarshalMsgpack() + }) + RegisterExtDecoder(extID, value, func(d *Decoder, v reflect.Value, extLen int) error { + b, err := d.readN(extLen) + if err != nil { + return err + } + return v.Interface().(Unmarshaler).UnmarshalMsgpack(b) + }) +} + +func UnregisterExt(extID int8) { + unregisterExtEncoder(extID) + unregisterExtDecoder(extID) +} + +func RegisterExtEncoder( + extID int8, + value interface{}, + encoder func(enc *Encoder, v reflect.Value) ([]byte, error), +) { + unregisterExtEncoder(extID) + + typ := reflect.TypeOf(value) + extEncoder := makeExtEncoder(extID, typ, encoder) + typeEncMap.Store(extID, typ) + typeEncMap.Store(typ, extEncoder) + if typ.Kind() == reflect.Ptr { + typeEncMap.Store(typ.Elem(), makeExtEncoderAddr(extEncoder)) + } +} + +func unregisterExtEncoder(extID int8) { + t, ok := typeEncMap.Load(extID) + if !ok { + return + } + typeEncMap.Delete(extID) + typ := t.(reflect.Type) + typeEncMap.Delete(typ) + if typ.Kind() == reflect.Ptr { + typeEncMap.Delete(typ.Elem()) + } +} + +func makeExtEncoder( + extID int8, + typ reflect.Type, + encoder func(enc *Encoder, v reflect.Value) ([]byte, error), +) encoderFunc { + nilable := typ.Kind() == reflect.Ptr + + return func(e *Encoder, v reflect.Value) error { + if nilable && v.IsNil() { + return e.EncodeNil() + } + + b, err := encoder(e, v) + if err != nil { + return err + } + + if err := e.EncodeExtHeader(extID, len(b)); err != nil { + return err + } + + return e.write(b) + } +} + +func makeExtEncoderAddr(extEncoder encoderFunc) encoderFunc { + return func(e *Encoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface()) + } + return extEncoder(e, v.Addr()) + } +} + +func RegisterExtDecoder( + extID int8, + value interface{}, + decoder func(dec *Decoder, v reflect.Value, extLen int) error, +) { + unregisterExtDecoder(extID) + + typ := reflect.TypeOf(value) + extDecoder := makeExtDecoder(extID, typ, decoder) + extTypes[extID] = &extInfo{ + Type: typ, + Decoder: decoder, + } + + typeDecMap.Store(extID, typ) + typeDecMap.Store(typ, extDecoder) + if typ.Kind() == reflect.Ptr { + typeDecMap.Store(typ.Elem(), makeExtDecoderAddr(extDecoder)) + } +} + +func unregisterExtDecoder(extID int8) { + t, ok := typeDecMap.Load(extID) + if !ok { + return + } + typeDecMap.Delete(extID) + delete(extTypes, extID) + typ := t.(reflect.Type) + typeDecMap.Delete(typ) + if typ.Kind() == reflect.Ptr { + typeDecMap.Delete(typ.Elem()) + } +} + +func makeExtDecoder( + wantedExtID int8, + typ reflect.Type, + decoder func(d *Decoder, v reflect.Value, extLen int) error, +) decoderFunc { + return nilAwareDecoder(typ, func(d *Decoder, v reflect.Value) error { + extID, extLen, err := d.DecodeExtHeader() + if err != nil { + return err + } + if extID != wantedExtID { + return fmt.Errorf("msgpack: got ext type=%d, wanted %d", extID, wantedExtID) + } + return decoder(d, v, extLen) + }) +} + +func makeExtDecoderAddr(extDecoder decoderFunc) decoderFunc { + return func(d *Decoder, v reflect.Value) error { + if !v.CanAddr() { + return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface()) + } + return extDecoder(d, v.Addr()) + } +} + +func (e *Encoder) EncodeExtHeader(extID int8, extLen int) error { + if err := e.encodeExtLen(extLen); err != nil { + return err + } + if err := e.w.WriteByte(byte(extID)); err != nil { + return err + } + return nil +} + +func (e *Encoder) encodeExtLen(l int) error { + switch l { + case 1: + return e.writeCode(msgpcode.FixExt1) + case 2: + return e.writeCode(msgpcode.FixExt2) + case 4: + return e.writeCode(msgpcode.FixExt4) + case 8: + return e.writeCode(msgpcode.FixExt8) + case 16: + return e.writeCode(msgpcode.FixExt16) + } + if l <= math.MaxUint8 { + return e.write1(msgpcode.Ext8, uint8(l)) + } + if l <= math.MaxUint16 { + return e.write2(msgpcode.Ext16, uint16(l)) + } + return e.write4(msgpcode.Ext32, uint32(l)) +} + +func (d *Decoder) DecodeExtHeader() (extID int8, extLen int, err error) { + c, err := d.readCode() + if err != nil { + return + } + return d.extHeader(c) +} + +func (d *Decoder) extHeader(c byte) (int8, int, error) { + extLen, err := d.parseExtLen(c) + if err != nil { + return 0, 0, err + } + + extID, err := d.readCode() + if err != nil { + return 0, 0, err + } + + return int8(extID), extLen, nil +} + +func (d *Decoder) parseExtLen(c byte) (int, error) { + switch c { + case msgpcode.FixExt1: + return 1, nil + case msgpcode.FixExt2: + return 2, nil + case msgpcode.FixExt4: + return 4, nil + case msgpcode.FixExt8: + return 8, nil + case msgpcode.FixExt16: + return 16, nil + case msgpcode.Ext8: + n, err := d.uint8() + return int(n), err + case msgpcode.Ext16: + n, err := d.uint16() + return int(n), err + case msgpcode.Ext32: + n, err := d.uint32() + return int(n), err + default: + return 0, fmt.Errorf("msgpack: invalid code=%x decoding ext len", c) + } +} + +func (d *Decoder) decodeInterfaceExt(c byte) (interface{}, error) { + extID, extLen, err := d.extHeader(c) + if err != nil { + return nil, err + } + + info, ok := extTypes[extID] + if !ok { + return nil, fmt.Errorf("msgpack: unknown ext id=%d", extID) + } + + v := reflect.New(info.Type).Elem() + if nilable(v.Kind()) && v.IsNil() { + v.Set(reflect.New(info.Type.Elem())) + } + + if err := info.Decoder(d, v, extLen); err != nil { + return nil, err + } + + return v.Interface(), nil +} + +func (d *Decoder) skipExt(c byte) error { + n, err := d.parseExtLen(c) + if err != nil { + return err + } + return d.skipN(n + 1) +} + +func (d *Decoder) skipExtHeader(c byte) error { + // Read ext type. + _, err := d.readCode() + if err != nil { + return err + } + // Read ext body len. + for i := 0; i < extHeaderLen(c); i++ { + _, err := d.readCode() + if err != nil { + return err + } + } + return nil +} + +func extHeaderLen(c byte) int { + switch c { + case msgpcode.Ext8: + return 1 + case msgpcode.Ext16: + return 2 + case msgpcode.Ext32: + return 4 + } + return 0 +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/intern.go b/vendor/github.com/vmihailenco/msgpack/v5/intern.go new file mode 100644 index 00000000..be0316a8 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/intern.go @@ -0,0 +1,238 @@ +package msgpack + +import ( + "fmt" + "math" + "reflect" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +const ( + minInternedStringLen = 3 + maxDictLen = math.MaxUint16 +) + +var internedStringExtID = int8(math.MinInt8) + +func init() { + extTypes[internedStringExtID] = &extInfo{ + Type: stringType, + Decoder: decodeInternedStringExt, + } +} + +func decodeInternedStringExt(d *Decoder, v reflect.Value, extLen int) error { + idx, err := d.decodeInternedStringIndex(extLen) + if err != nil { + return err + } + + s, err := d.internedStringAtIndex(idx) + if err != nil { + return err + } + + v.SetString(s) + return nil +} + +//------------------------------------------------------------------------------ + +func encodeInternedInterfaceValue(e *Encoder, v reflect.Value) error { + if v.IsNil() { + return e.EncodeNil() + } + + v = v.Elem() + if v.Kind() == reflect.String { + return e.encodeInternedString(v.String(), true) + } + return e.EncodeValue(v) +} + +func encodeInternedStringValue(e *Encoder, v reflect.Value) error { + return e.encodeInternedString(v.String(), true) +} + +func (e *Encoder) encodeInternedString(s string, intern bool) error { + // Interned string takes at least 3 bytes. Plain string 1 byte + string len. + if len(s) >= minInternedStringLen { + if idx, ok := e.dict[s]; ok { + return e.encodeInternedStringIndex(idx) + } + + if intern && len(e.dict) < maxDictLen { + if e.dict == nil { + e.dict = make(map[string]int) + } + idx := len(e.dict) + e.dict[s] = idx + } + } + + return e.encodeNormalString(s) +} + +func (e *Encoder) encodeInternedStringIndex(idx int) error { + if idx <= math.MaxUint8 { + if err := e.writeCode(msgpcode.FixExt1); err != nil { + return err + } + return e.write1(byte(internedStringExtID), uint8(idx)) + } + + if idx <= math.MaxUint16 { + if err := e.writeCode(msgpcode.FixExt2); err != nil { + return err + } + return e.write2(byte(internedStringExtID), uint16(idx)) + } + + if uint64(idx) <= math.MaxUint32 { + if err := e.writeCode(msgpcode.FixExt4); err != nil { + return err + } + return e.write4(byte(internedStringExtID), uint32(idx)) + } + + return fmt.Errorf("msgpack: interned string index=%d is too large", idx) +} + +//------------------------------------------------------------------------------ + +func decodeInternedInterfaceValue(d *Decoder, v reflect.Value) error { + s, err := d.decodeInternedString(true) + if err == nil { + v.Set(reflect.ValueOf(s)) + return nil + } + if err != nil { + if _, ok := err.(unexpectedCodeError); !ok { + return err + } + } + + if err := d.s.UnreadByte(); err != nil { + return err + } + return decodeInterfaceValue(d, v) +} + +func decodeInternedStringValue(d *Decoder, v reflect.Value) error { + s, err := d.decodeInternedString(true) + if err != nil { + return err + } + + v.SetString(s) + return nil +} + +func (d *Decoder) decodeInternedString(intern bool) (string, error) { + c, err := d.readCode() + if err != nil { + return "", err + } + + if msgpcode.IsFixedString(c) { + n := int(c & msgpcode.FixedStrMask) + return d.decodeInternedStringWithLen(n, intern) + } + + switch c { + case msgpcode.Nil: + return "", nil + case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4: + typeID, extLen, err := d.extHeader(c) + if err != nil { + return "", err + } + if typeID != internedStringExtID { + err := fmt.Errorf("msgpack: got ext type=%d, wanted %d", + typeID, internedStringExtID) + return "", err + } + + idx, err := d.decodeInternedStringIndex(extLen) + if err != nil { + return "", err + } + + return d.internedStringAtIndex(idx) + case msgpcode.Str8, msgpcode.Bin8: + n, err := d.uint8() + if err != nil { + return "", err + } + return d.decodeInternedStringWithLen(int(n), intern) + case msgpcode.Str16, msgpcode.Bin16: + n, err := d.uint16() + if err != nil { + return "", err + } + return d.decodeInternedStringWithLen(int(n), intern) + case msgpcode.Str32, msgpcode.Bin32: + n, err := d.uint32() + if err != nil { + return "", err + } + return d.decodeInternedStringWithLen(int(n), intern) + } + + return "", unexpectedCodeError{ + code: c, + hint: "interned string", + } +} + +func (d *Decoder) decodeInternedStringIndex(extLen int) (int, error) { + switch extLen { + case 1: + n, err := d.uint8() + if err != nil { + return 0, err + } + return int(n), nil + case 2: + n, err := d.uint16() + if err != nil { + return 0, err + } + return int(n), nil + case 4: + n, err := d.uint32() + if err != nil { + return 0, err + } + return int(n), nil + } + + err := fmt.Errorf("msgpack: unsupported ext len=%d decoding interned string", extLen) + return 0, err +} + +func (d *Decoder) internedStringAtIndex(idx int) (string, error) { + if idx >= len(d.dict) { + err := fmt.Errorf("msgpack: interned string at index=%d does not exist", idx) + return "", err + } + return d.dict[idx], nil +} + +func (d *Decoder) decodeInternedStringWithLen(n int, intern bool) (string, error) { + if n <= 0 { + return "", nil + } + + s, err := d.stringWithLen(n) + if err != nil { + return "", err + } + + if intern && len(s) >= minInternedStringLen && len(d.dict) < maxDictLen { + d.dict = append(d.dict, s) + } + + return s, nil +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/msgpack.go b/vendor/github.com/vmihailenco/msgpack/v5/msgpack.go new file mode 100644 index 00000000..4db2fa2c --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/msgpack.go @@ -0,0 +1,52 @@ +package msgpack + +import "fmt" + +type Marshaler interface { + MarshalMsgpack() ([]byte, error) +} + +type Unmarshaler interface { + UnmarshalMsgpack([]byte) error +} + +type CustomEncoder interface { + EncodeMsgpack(*Encoder) error +} + +type CustomDecoder interface { + DecodeMsgpack(*Decoder) error +} + +//------------------------------------------------------------------------------ + +type RawMessage []byte + +var ( + _ CustomEncoder = (RawMessage)(nil) + _ CustomDecoder = (*RawMessage)(nil) +) + +func (m RawMessage) EncodeMsgpack(enc *Encoder) error { + return enc.write(m) +} + +func (m *RawMessage) DecodeMsgpack(dec *Decoder) error { + msg, err := dec.DecodeRaw() + if err != nil { + return err + } + *m = msg + return nil +} + +//------------------------------------------------------------------------------ + +type unexpectedCodeError struct { + code byte + hint string +} + +func (err unexpectedCodeError) Error() string { + return fmt.Sprintf("msgpack: unexpected code=%x decoding %s", err.code, err.hint) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/msgpcode/msgpcode.go b/vendor/github.com/vmihailenco/msgpack/v5/msgpcode/msgpcode.go new file mode 100644 index 00000000..e35389cc --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/msgpcode/msgpcode.go @@ -0,0 +1,88 @@ +package msgpcode + +var ( + PosFixedNumHigh byte = 0x7f + NegFixedNumLow byte = 0xe0 + + Nil byte = 0xc0 + + False byte = 0xc2 + True byte = 0xc3 + + Float byte = 0xca + Double byte = 0xcb + + Uint8 byte = 0xcc + Uint16 byte = 0xcd + Uint32 byte = 0xce + Uint64 byte = 0xcf + + Int8 byte = 0xd0 + Int16 byte = 0xd1 + Int32 byte = 0xd2 + Int64 byte = 0xd3 + + FixedStrLow byte = 0xa0 + FixedStrHigh byte = 0xbf + FixedStrMask byte = 0x1f + Str8 byte = 0xd9 + Str16 byte = 0xda + Str32 byte = 0xdb + + Bin8 byte = 0xc4 + Bin16 byte = 0xc5 + Bin32 byte = 0xc6 + + FixedArrayLow byte = 0x90 + FixedArrayHigh byte = 0x9f + FixedArrayMask byte = 0xf + Array16 byte = 0xdc + Array32 byte = 0xdd + + FixedMapLow byte = 0x80 + FixedMapHigh byte = 0x8f + FixedMapMask byte = 0xf + Map16 byte = 0xde + Map32 byte = 0xdf + + FixExt1 byte = 0xd4 + FixExt2 byte = 0xd5 + FixExt4 byte = 0xd6 + FixExt8 byte = 0xd7 + FixExt16 byte = 0xd8 + Ext8 byte = 0xc7 + Ext16 byte = 0xc8 + Ext32 byte = 0xc9 +) + +func IsFixedNum(c byte) bool { + return c <= PosFixedNumHigh || c >= NegFixedNumLow +} + +func IsFixedMap(c byte) bool { + return c >= FixedMapLow && c <= FixedMapHigh +} + +func IsFixedArray(c byte) bool { + return c >= FixedArrayLow && c <= FixedArrayHigh +} + +func IsFixedString(c byte) bool { + return c >= FixedStrLow && c <= FixedStrHigh +} + +func IsString(c byte) bool { + return IsFixedString(c) || c == Str8 || c == Str16 || c == Str32 +} + +func IsBin(c byte) bool { + return c == Bin8 || c == Bin16 || c == Bin32 +} + +func IsFixedExt(c byte) bool { + return c >= FixExt1 && c <= FixExt16 +} + +func IsExt(c byte) bool { + return IsFixedExt(c) || c == Ext8 || c == Ext16 || c == Ext32 +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/package.json b/vendor/github.com/vmihailenco/msgpack/v5/package.json new file mode 100644 index 00000000..298910d4 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/package.json @@ -0,0 +1,4 @@ +{ + "name": "msgpack", + "version": "5.3.5" +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/safe.go b/vendor/github.com/vmihailenco/msgpack/v5/safe.go new file mode 100644 index 00000000..8352c9dc --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/safe.go @@ -0,0 +1,13 @@ +// +build appengine + +package msgpack + +// bytesToString converts byte slice to string. +func bytesToString(b []byte) string { + return string(b) +} + +// stringToBytes converts string to byte slice. +func stringToBytes(s string) []byte { + return []byte(s) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/time.go b/vendor/github.com/vmihailenco/msgpack/v5/time.go new file mode 100644 index 00000000..44566ec0 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/time.go @@ -0,0 +1,145 @@ +package msgpack + +import ( + "encoding/binary" + "fmt" + "reflect" + "time" + + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +var timeExtID int8 = -1 + +func init() { + RegisterExtEncoder(timeExtID, time.Time{}, timeEncoder) + RegisterExtDecoder(timeExtID, time.Time{}, timeDecoder) +} + +func timeEncoder(e *Encoder, v reflect.Value) ([]byte, error) { + return e.encodeTime(v.Interface().(time.Time)), nil +} + +func timeDecoder(d *Decoder, v reflect.Value, extLen int) error { + tm, err := d.decodeTime(extLen) + if err != nil { + return err + } + + ptr := v.Addr().Interface().(*time.Time) + *ptr = tm + + return nil +} + +func (e *Encoder) EncodeTime(tm time.Time) error { + b := e.encodeTime(tm) + if err := e.encodeExtLen(len(b)); err != nil { + return err + } + if err := e.w.WriteByte(byte(timeExtID)); err != nil { + return err + } + return e.write(b) +} + +func (e *Encoder) encodeTime(tm time.Time) []byte { + if e.timeBuf == nil { + e.timeBuf = make([]byte, 12) + } + + secs := uint64(tm.Unix()) + if secs>>34 == 0 { + data := uint64(tm.Nanosecond())<<34 | secs + + if data&0xffffffff00000000 == 0 { + b := e.timeBuf[:4] + binary.BigEndian.PutUint32(b, uint32(data)) + return b + } + + b := e.timeBuf[:8] + binary.BigEndian.PutUint64(b, data) + return b + } + + b := e.timeBuf[:12] + binary.BigEndian.PutUint32(b, uint32(tm.Nanosecond())) + binary.BigEndian.PutUint64(b[4:], secs) + return b +} + +func (d *Decoder) DecodeTime() (time.Time, error) { + c, err := d.readCode() + if err != nil { + return time.Time{}, err + } + + // Legacy format. + if c == msgpcode.FixedArrayLow|2 { + sec, err := d.DecodeInt64() + if err != nil { + return time.Time{}, err + } + + nsec, err := d.DecodeInt64() + if err != nil { + return time.Time{}, err + } + + return time.Unix(sec, nsec), nil + } + + if msgpcode.IsString(c) { + s, err := d.string(c) + if err != nil { + return time.Time{}, err + } + return time.Parse(time.RFC3339Nano, s) + } + + extID, extLen, err := d.extHeader(c) + if err != nil { + return time.Time{}, err + } + + if extID != timeExtID { + return time.Time{}, fmt.Errorf("msgpack: invalid time ext id=%d", extID) + } + + tm, err := d.decodeTime(extLen) + if err != nil { + return tm, err + } + + if tm.IsZero() { + // Zero time does not have timezone information. + return tm.UTC(), nil + } + return tm, nil +} + +func (d *Decoder) decodeTime(extLen int) (time.Time, error) { + b, err := d.readN(extLen) + if err != nil { + return time.Time{}, err + } + + switch len(b) { + case 4: + sec := binary.BigEndian.Uint32(b) + return time.Unix(int64(sec), 0), nil + case 8: + sec := binary.BigEndian.Uint64(b) + nsec := int64(sec >> 34) + sec &= 0x00000003ffffffff + return time.Unix(int64(sec), nsec), nil + case 12: + nsec := binary.BigEndian.Uint32(b) + sec := binary.BigEndian.Uint64(b[4:]) + return time.Unix(int64(sec), int64(nsec)), nil + default: + err = fmt.Errorf("msgpack: invalid ext len=%d decoding time", extLen) + return time.Time{}, err + } +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/types.go b/vendor/github.com/vmihailenco/msgpack/v5/types.go new file mode 100644 index 00000000..69aca611 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/types.go @@ -0,0 +1,407 @@ +package msgpack + +import ( + "encoding" + "fmt" + "log" + "reflect" + "sync" + + "github.com/vmihailenco/tagparser/v2" +) + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +var ( + customEncoderType = reflect.TypeOf((*CustomEncoder)(nil)).Elem() + customDecoderType = reflect.TypeOf((*CustomDecoder)(nil)).Elem() +) + +var ( + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() +) + +var ( + binaryMarshalerType = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem() + binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem() +) + +var ( + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +type ( + encoderFunc func(*Encoder, reflect.Value) error + decoderFunc func(*Decoder, reflect.Value) error +) + +var ( + typeEncMap sync.Map + typeDecMap sync.Map +) + +// Register registers encoder and decoder functions for a value. +// This is low level API and in most cases you should prefer implementing +// CustomEncoder/CustomDecoder or Marshaler/Unmarshaler interfaces. +func Register(value interface{}, enc encoderFunc, dec decoderFunc) { + typ := reflect.TypeOf(value) + if enc != nil { + typeEncMap.Store(typ, enc) + } + if dec != nil { + typeDecMap.Store(typ, dec) + } +} + +//------------------------------------------------------------------------------ + +const defaultStructTag = "msgpack" + +var structs = newStructCache() + +type structCache struct { + m sync.Map +} + +type structCacheKey struct { + tag string + typ reflect.Type +} + +func newStructCache() *structCache { + return new(structCache) +} + +func (m *structCache) Fields(typ reflect.Type, tag string) *fields { + key := structCacheKey{tag: tag, typ: typ} + + if v, ok := m.m.Load(key); ok { + return v.(*fields) + } + + fs := getFields(typ, tag) + m.m.Store(key, fs) + + return fs +} + +//------------------------------------------------------------------------------ + +type field struct { + name string + index []int + omitEmpty bool + encoder encoderFunc + decoder decoderFunc +} + +func (f *field) Omit(strct reflect.Value, forced bool) bool { + v, ok := fieldByIndex(strct, f.index) + if !ok { + return true + } + return (f.omitEmpty || forced) && isEmptyValue(v) +} + +func (f *field) EncodeValue(e *Encoder, strct reflect.Value) error { + v, ok := fieldByIndex(strct, f.index) + if !ok { + return e.EncodeNil() + } + return f.encoder(e, v) +} + +func (f *field) DecodeValue(d *Decoder, strct reflect.Value) error { + v := fieldByIndexAlloc(strct, f.index) + return f.decoder(d, v) +} + +//------------------------------------------------------------------------------ + +type fields struct { + Type reflect.Type + Map map[string]*field + List []*field + AsArray bool + + hasOmitEmpty bool +} + +func newFields(typ reflect.Type) *fields { + return &fields{ + Type: typ, + Map: make(map[string]*field, typ.NumField()), + List: make([]*field, 0, typ.NumField()), + } +} + +func (fs *fields) Add(field *field) { + fs.warnIfFieldExists(field.name) + fs.Map[field.name] = field + fs.List = append(fs.List, field) + if field.omitEmpty { + fs.hasOmitEmpty = true + } +} + +func (fs *fields) warnIfFieldExists(name string) { + if _, ok := fs.Map[name]; ok { + log.Printf("msgpack: %s already has field=%s", fs.Type, name) + } +} + +func (fs *fields) OmitEmpty(strct reflect.Value, forced bool) []*field { + if !fs.hasOmitEmpty && !forced { + return fs.List + } + + fields := make([]*field, 0, len(fs.List)) + + for _, f := range fs.List { + if !f.Omit(strct, forced) { + fields = append(fields, f) + } + } + + return fields +} + +func getFields(typ reflect.Type, fallbackTag string) *fields { + fs := newFields(typ) + + var omitEmpty bool + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + + tagStr := f.Tag.Get(defaultStructTag) + if tagStr == "" && fallbackTag != "" { + tagStr = f.Tag.Get(fallbackTag) + } + + tag := tagparser.Parse(tagStr) + if tag.Name == "-" { + continue + } + + if f.Name == "_msgpack" { + fs.AsArray = tag.HasOption("as_array") || tag.HasOption("asArray") + if tag.HasOption("omitempty") { + omitEmpty = true + } + } + + if f.PkgPath != "" && !f.Anonymous { + continue + } + + field := &field{ + name: tag.Name, + index: f.Index, + omitEmpty: omitEmpty || tag.HasOption("omitempty"), + } + + if tag.HasOption("intern") { + switch f.Type.Kind() { + case reflect.Interface: + field.encoder = encodeInternedInterfaceValue + field.decoder = decodeInternedInterfaceValue + case reflect.String: + field.encoder = encodeInternedStringValue + field.decoder = decodeInternedStringValue + default: + err := fmt.Errorf("msgpack: intern strings are not supported on %s", f.Type) + panic(err) + } + } else { + field.encoder = getEncoder(f.Type) + field.decoder = getDecoder(f.Type) + } + + if field.name == "" { + field.name = f.Name + } + + if f.Anonymous && !tag.HasOption("noinline") { + inline := tag.HasOption("inline") + if inline { + inlineFields(fs, f.Type, field, fallbackTag) + } else { + inline = shouldInline(fs, f.Type, field, fallbackTag) + } + + if inline { + if _, ok := fs.Map[field.name]; ok { + log.Printf("msgpack: %s already has field=%s", fs.Type, field.name) + } + fs.Map[field.name] = field + continue + } + } + + fs.Add(field) + + if alias, ok := tag.Options["alias"]; ok { + fs.warnIfFieldExists(alias) + fs.Map[alias] = field + } + } + return fs +} + +var ( + encodeStructValuePtr uintptr + decodeStructValuePtr uintptr +) + +//nolint:gochecknoinits +func init() { + encodeStructValuePtr = reflect.ValueOf(encodeStructValue).Pointer() + decodeStructValuePtr = reflect.ValueOf(decodeStructValue).Pointer() +} + +func inlineFields(fs *fields, typ reflect.Type, f *field, tag string) { + inlinedFields := getFields(typ, tag).List + for _, field := range inlinedFields { + if _, ok := fs.Map[field.name]; ok { + // Don't inline shadowed fields. + continue + } + field.index = append(f.index, field.index...) + fs.Add(field) + } +} + +func shouldInline(fs *fields, typ reflect.Type, f *field, tag string) bool { + var encoder encoderFunc + var decoder decoderFunc + + if typ.Kind() == reflect.Struct { + encoder = f.encoder + decoder = f.decoder + } else { + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + encoder = getEncoder(typ) + decoder = getDecoder(typ) + } + if typ.Kind() != reflect.Struct { + return false + } + } + + if reflect.ValueOf(encoder).Pointer() != encodeStructValuePtr { + return false + } + if reflect.ValueOf(decoder).Pointer() != decodeStructValuePtr { + return false + } + + inlinedFields := getFields(typ, tag).List + for _, field := range inlinedFields { + if _, ok := fs.Map[field.name]; ok { + // Don't auto inline if there are shadowed fields. + return false + } + } + + for _, field := range inlinedFields { + field.index = append(f.index, field.index...) + fs.Add(field) + } + return true +} + +type isZeroer interface { + IsZero() bool +} + +func isEmptyValue(v reflect.Value) bool { + kind := v.Kind() + + for kind == reflect.Interface { + if v.IsNil() { + return true + } + v = v.Elem() + kind = v.Kind() + } + + if z, ok := v.Interface().(isZeroer); ok { + return nilable(kind) && v.IsNil() || z.IsZero() + } + + switch kind { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Ptr: + return v.IsNil() + default: + return false + } +} + +func fieldByIndex(v reflect.Value, index []int) (_ reflect.Value, ok bool) { + if len(index) == 1 { + return v.Field(index[0]), true + } + + for i, idx := range index { + if i > 0 { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return v, false + } + v = v.Elem() + } + } + v = v.Field(idx) + } + + return v, true +} + +func fieldByIndexAlloc(v reflect.Value, index []int) reflect.Value { + if len(index) == 1 { + return v.Field(index[0]) + } + + for i, idx := range index { + if i > 0 { + var ok bool + v, ok = indirectNil(v) + if !ok { + return v + } + } + v = v.Field(idx) + } + + return v +} + +func indirectNil(v reflect.Value) (reflect.Value, bool) { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + if !v.CanSet() { + return v, false + } + elemType := v.Type().Elem() + if elemType.Kind() != reflect.Struct { + return v, false + } + v.Set(reflect.New(elemType)) + } + v = v.Elem() + } + return v, true +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/unsafe.go b/vendor/github.com/vmihailenco/msgpack/v5/unsafe.go new file mode 100644 index 00000000..192ac479 --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/unsafe.go @@ -0,0 +1,22 @@ +// +build !appengine + +package msgpack + +import ( + "unsafe" +) + +// bytesToString converts byte slice to string. +func bytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// stringToBytes converts string to byte slice. +func stringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/vendor/github.com/vmihailenco/msgpack/v5/version.go b/vendor/github.com/vmihailenco/msgpack/v5/version.go new file mode 100644 index 00000000..1d49337c --- /dev/null +++ b/vendor/github.com/vmihailenco/msgpack/v5/version.go @@ -0,0 +1,6 @@ +package msgpack + +// Version is the current release version. +func Version() string { + return "5.3.5" +} diff --git a/vendor/github.com/vmihailenco/tagparser/v2/.travis.yml b/vendor/github.com/vmihailenco/tagparser/v2/.travis.yml new file mode 100644 index 00000000..7194cd00 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/.travis.yml @@ -0,0 +1,19 @@ +dist: xenial +language: go + +go: + - 1.14.x + - 1.15.x + - tip + +matrix: + allow_failures: + - go: tip + +env: + - GO111MODULE=on + +go_import_path: github.com/vmihailenco/tagparser + +before_install: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 diff --git a/vendor/github.com/vmihailenco/tagparser/v2/LICENSE b/vendor/github.com/vmihailenco/tagparser/v2/LICENSE new file mode 100644 index 00000000..3fc93fdf --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2019 The github.com/vmihailenco/tagparser Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/vmihailenco/tagparser/v2/Makefile b/vendor/github.com/vmihailenco/tagparser/v2/Makefile new file mode 100644 index 00000000..0b1b5959 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/Makefile @@ -0,0 +1,9 @@ +all: + go test ./... + go test ./... -short -race + go test ./... -run=NONE -bench=. -benchmem + env GOOS=linux GOARCH=386 go test ./... + go vet ./... + go get github.com/gordonklaus/ineffassign + ineffassign . + golangci-lint run diff --git a/vendor/github.com/vmihailenco/tagparser/v2/README.md b/vendor/github.com/vmihailenco/tagparser/v2/README.md new file mode 100644 index 00000000..c0259de5 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/README.md @@ -0,0 +1,24 @@ +# Opinionated Golang tag parser + +[![Build Status](https://travis-ci.org/vmihailenco/tagparser.png?branch=master)](https://travis-ci.org/vmihailenco/tagparser) +[![GoDoc](https://godoc.org/github.com/vmihailenco/tagparser?status.svg)](https://godoc.org/github.com/vmihailenco/tagparser) + +## Installation + +Install: + +```shell +go get github.com/vmihailenco/tagparser/v2 +``` + +## Quickstart + +```go +func ExampleParse() { + tag := tagparser.Parse("some_name,key:value,key2:'complex value'") + fmt.Println(tag.Name) + fmt.Println(tag.Options) + // Output: some_name + // map[key:value key2:'complex value'] +} +``` diff --git a/vendor/github.com/vmihailenco/tagparser/v2/internal/parser/parser.go b/vendor/github.com/vmihailenco/tagparser/v2/internal/parser/parser.go new file mode 100644 index 00000000..21a9bc7f --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/internal/parser/parser.go @@ -0,0 +1,82 @@ +package parser + +import ( + "bytes" + + "github.com/vmihailenco/tagparser/v2/internal" +) + +type Parser struct { + b []byte + i int +} + +func New(b []byte) *Parser { + return &Parser{ + b: b, + } +} + +func NewString(s string) *Parser { + return New(internal.StringToBytes(s)) +} + +func (p *Parser) Bytes() []byte { + return p.b[p.i:] +} + +func (p *Parser) Valid() bool { + return p.i < len(p.b) +} + +func (p *Parser) Read() byte { + if p.Valid() { + c := p.b[p.i] + p.Advance() + return c + } + return 0 +} + +func (p *Parser) Peek() byte { + if p.Valid() { + return p.b[p.i] + } + return 0 +} + +func (p *Parser) Advance() { + p.i++ +} + +func (p *Parser) Skip(skip byte) bool { + if p.Peek() == skip { + p.Advance() + return true + } + return false +} + +func (p *Parser) SkipBytes(skip []byte) bool { + if len(skip) > len(p.b[p.i:]) { + return false + } + if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) { + return false + } + p.i += len(skip) + return true +} + +func (p *Parser) ReadSep(sep byte) ([]byte, bool) { + ind := bytes.IndexByte(p.b[p.i:], sep) + if ind == -1 { + b := p.b[p.i:] + p.i = len(p.b) + return b, false + } + + b := p.b[p.i : p.i+ind] + p.i += ind + 1 + return b, true +} diff --git a/vendor/github.com/vmihailenco/tagparser/v2/internal/safe.go b/vendor/github.com/vmihailenco/tagparser/v2/internal/safe.go new file mode 100644 index 00000000..870fe541 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/internal/safe.go @@ -0,0 +1,11 @@ +// +build appengine + +package internal + +func BytesToString(b []byte) string { + return string(b) +} + +func StringToBytes(s string) []byte { + return []byte(s) +} diff --git a/vendor/github.com/vmihailenco/tagparser/v2/internal/unsafe.go b/vendor/github.com/vmihailenco/tagparser/v2/internal/unsafe.go new file mode 100644 index 00000000..f8bc18d9 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/internal/unsafe.go @@ -0,0 +1,22 @@ +// +build !appengine + +package internal + +import ( + "unsafe" +) + +// BytesToString converts byte slice to string. +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// StringToBytes converts string to byte slice. +func StringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/vendor/github.com/vmihailenco/tagparser/v2/tagparser.go b/vendor/github.com/vmihailenco/tagparser/v2/tagparser.go new file mode 100644 index 00000000..5002e645 --- /dev/null +++ b/vendor/github.com/vmihailenco/tagparser/v2/tagparser.go @@ -0,0 +1,166 @@ +package tagparser + +import ( + "strings" + + "github.com/vmihailenco/tagparser/v2/internal/parser" +) + +type Tag struct { + Name string + Options map[string]string +} + +func (t *Tag) HasOption(name string) bool { + _, ok := t.Options[name] + return ok +} + +func Parse(s string) *Tag { + p := &tagParser{ + Parser: parser.NewString(s), + } + p.parseKey() + return &p.Tag +} + +type tagParser struct { + *parser.Parser + + Tag Tag + hasName bool + key string +} + +func (p *tagParser) setTagOption(key, value string) { + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + + if !p.hasName { + p.hasName = true + if key == "" { + p.Tag.Name = value + return + } + } + if p.Tag.Options == nil { + p.Tag.Options = make(map[string]string) + } + if key == "" { + p.Tag.Options[value] = "" + } else { + p.Tag.Options[key] = value + } +} + +func (p *tagParser) parseKey() { + p.key = "" + + var b []byte + for p.Valid() { + c := p.Read() + switch c { + case ',': + p.Skip(' ') + p.setTagOption("", string(b)) + p.parseKey() + return + case ':': + p.key = string(b) + p.parseValue() + return + case '\'': + p.parseQuotedValue() + return + default: + b = append(b, c) + } + } + + if len(b) > 0 { + p.setTagOption("", string(b)) + } +} + +func (p *tagParser) parseValue() { + const quote = '\'' + c := p.Peek() + if c == quote { + p.Skip(quote) + p.parseQuotedValue() + return + } + + var b []byte + for p.Valid() { + c = p.Read() + switch c { + case '\\': + b = append(b, p.Read()) + case '(': + b = append(b, c) + b = p.readBrackets(b) + case ',': + p.Skip(' ') + p.setTagOption(p.key, string(b)) + p.parseKey() + return + default: + b = append(b, c) + } + } + p.setTagOption(p.key, string(b)) +} + +func (p *tagParser) readBrackets(b []byte) []byte { + var lvl int +loop: + for p.Valid() { + c := p.Read() + switch c { + case '\\': + b = append(b, p.Read()) + case '(': + b = append(b, c) + lvl++ + case ')': + b = append(b, c) + lvl-- + if lvl < 0 { + break loop + } + default: + b = append(b, c) + } + } + return b +} + +func (p *tagParser) parseQuotedValue() { + const quote = '\'' + var b []byte + for p.Valid() { + bb, ok := p.ReadSep(quote) + if !ok { + b = append(b, bb...) + break + } + + // keep the escaped single-quote, and continue until we've found the + // one that isn't. + if len(bb) > 0 && bb[len(bb)-1] == '\\' { + b = append(b, bb[:len(bb)-1]...) + b = append(b, quote) + continue + } + + b = append(b, bb...) + break + } + + p.setTagOption(p.key, string(b)) + if p.Skip(',') { + p.Skip(' ') + } + p.parseKey() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 40a4cde2..956a9b6c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -268,6 +268,9 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util +# github.com/r3labs/diff/v3 v3.0.1 +## explicit; go 1.13 +github.com/r3labs/diff/v3 # github.com/spf13/cobra v1.7.0 ## explicit; go 1.15 github.com/spf13/cobra @@ -280,6 +283,15 @@ github.com/stolostron/siteconfig/api/v1alpha1 # github.com/thoas/go-funk v0.9.3 ## explicit; go 1.13 github.com/thoas/go-funk +# github.com/vmihailenco/msgpack/v5 v5.3.5 +## explicit; go 1.11 +github.com/vmihailenco/msgpack/v5 +github.com/vmihailenco/msgpack/v5/msgpcode +# github.com/vmihailenco/tagparser/v2 v2.0.0 +## explicit; go 1.15 +github.com/vmihailenco/tagparser/v2 +github.com/vmihailenco/tagparser/v2/internal +github.com/vmihailenco/tagparser/v2/internal/parser # github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb ## explicit github.com/xeipuuv/gojsonpointer