diff --git a/data/blood/glucose/glucose.go b/data/blood/glucose/glucose.go index 32ca889dd9..33ac5d3f99 100644 --- a/data/blood/glucose/glucose.go +++ b/data/blood/glucose/glucose.go @@ -67,3 +67,14 @@ func NormalizeValueForUnits(value *float64, units *string) *float64 { } return value } + +func NormalizeValueForUnitsWithFullPrecision(value *float64, units *string) *float64 { + if value != nil && units != nil { + switch *units { + case MgdL, Mgdl: + floatValue := *value / MmolLToMgdLConversionFactor + return &floatValue + } + } + return value +} diff --git a/data/blood/glucose/glucose_test.go b/data/blood/glucose/glucose_test.go index cf726fd967..f61942a14b 100644 --- a/data/blood/glucose/glucose_test.go +++ b/data/blood/glucose/glucose_test.go @@ -125,4 +125,28 @@ var _ = Describe("Glucose", func() { } }) }) + Context("NormalizeValueForUnitsWithFullPrecision", func() { + DescribeTable("given value and units", + func(value *float64, units *string, expectedValue *float64) { + actualValue := glucose.NormalizeValueForUnitsWithFullPrecision(value, units) + if expectedValue == nil { + Expect(actualValue).To(BeNil()) + } else { + Expect(actualValue).ToNot(BeNil()) + Expect(*actualValue).To(Equal(*expectedValue)) + } + }, + Entry("returns nil for nil value", nil, pointer.FromString("mmol/L"), nil), + Entry("returns unchanged value for nil units", pointer.FromFloat64(10.0), nil, pointer.FromFloat64(10.0)), + Entry("returns unchanged value for unknown units", pointer.FromFloat64(10.0), pointer.FromString("unknown"), pointer.FromFloat64(10.0)), + Entry("returns unchanged value for mmol/L units", pointer.FromFloat64(10.0), pointer.FromString("mmol/L"), pointer.FromFloat64(10.0)), + Entry("returns unchanged value for mmol/l units", pointer.FromFloat64(10.0), pointer.FromString("mmol/l"), pointer.FromFloat64(10.0)), + + Entry("returns converted value for mg/dL units", pointer.FromFloat64(140.0), pointer.FromString("mg/dL"), pointer.FromFloat64(7.771047187463747)), + Entry("returns converted value for mg/dL units", pointer.FromFloat64(100.0), pointer.FromString("mg/dL"), pointer.FromFloat64(5.550747991045533)), + Entry("returns converted value for mg/dl units", pointer.FromFloat64(80.0), pointer.FromString("mg/dl"), pointer.FromFloat64(4.440598392836427)), + Entry("returns converted value for mg/dl units", pointer.FromFloat64(69.0), pointer.FromString("mg/dl"), pointer.FromFloat64(3.830016113821418)), + Entry("returns converted value for mg/dl units", pointer.FromFloat64(50.0), pointer.FromString("mg/dl"), pointer.FromFloat64(2.7753739955227665)), + ) + }) }) diff --git a/data/blood/glucose/test/glucose.go b/data/blood/glucose/test/glucose.go index d170a86666..a877cd7b43 100644 --- a/data/blood/glucose/test/glucose.go +++ b/data/blood/glucose/test/glucose.go @@ -1,10 +1,31 @@ package test import ( + "github.com/onsi/gomega" + dataBloodGlucose "github.com/tidepool-org/platform/data/blood/glucose" + "github.com/tidepool-org/platform/metadata" "github.com/tidepool-org/platform/test" ) func RandomUnits() string { return test.RandomStringFromArray(dataBloodGlucose.Units()) } + +func ExpectRaw(raw *metadata.Metadata, expectedRaw *metadata.Metadata) { + if expectedRaw != nil { + gomega.Expect(raw).ToNot(gomega.BeNil()) + if units := expectedRaw.Get("units"); units == nil { + gomega.Expect(raw.Get("units")).To(gomega.BeNil()) + } else { + gomega.Expect(raw.Get("units")).To(gomega.Equal(units)) + } + if value := expectedRaw.Get("value"); value == nil { + gomega.Expect(raw.Get("value")).To(gomega.BeNil()) + } else { + gomega.Expect(raw.Get("value")).To(gomega.Equal(value)) + } + } else { + gomega.Expect(raw).To(gomega.BeNil()) + } +} diff --git a/data/data_set.go b/data/data_set.go index ee3456e98a..63e65d44d5 100644 --- a/data/data_set.go +++ b/data/data_set.go @@ -111,6 +111,7 @@ func (d *DataSetClient) Validate(validator structure.Validator) { type DataSetFilter struct { ClientName *string + LegacyOnly *bool Deleted *bool DeviceID *string } @@ -321,6 +322,7 @@ type DataSet struct { Type string `json:"type,omitempty" bson:"type,omitempty"` UploadID *string `json:"uploadId,omitempty" bson:"uploadId,omitempty"` UserID *string `json:"-" bson:"_userId,omitempty"` + LegacyGroupID *string `json:"-" bson:"_groupId,omitempty"` Version *string `json:"version,omitempty" bson:"version,omitempty"` VersionInternal int `json:"-" bson:"_version,omitempty"` } diff --git a/data/datum.go b/data/datum.go index a233e01940..f00868e495 100644 --- a/data/datum.go +++ b/data/datum.go @@ -15,7 +15,7 @@ type Datum interface { Validate(validator structure.Validator) Normalize(normalizer Normalizer) - IdentityFields() ([]string, error) + IdentityFields(version int) ([]string, error) GetOrigin() *origin.Origin GetPayload() *metadata.Metadata diff --git a/data/deduplicator/deduplicator/base.go b/data/deduplicator/deduplicator/base.go index 19ad2ae6df..b6bf6dcc36 100644 --- a/data/deduplicator/deduplicator/base.go +++ b/data/deduplicator/deduplicator/base.go @@ -59,9 +59,16 @@ func (b *Base) Open(ctx context.Context, repository dataStore.DataRepository, da update := data.NewDataSetUpdate() update.Active = pointer.FromBool(dataSet.Active) - update.Deduplicator = data.NewDeduplicatorDescriptor() - update.Deduplicator.Name = pointer.FromString(b.name) - update.Deduplicator.Version = pointer.FromString(b.version) + update.Deduplicator = dataSet.Deduplicator + if update.Deduplicator == nil { + update.Deduplicator = data.NewDeduplicatorDescriptor() + } + if update.Deduplicator.Name == nil { + update.Deduplicator.Name = pointer.FromString(b.name) + } + if update.Deduplicator.Version == nil { + update.Deduplicator.Version = pointer.FromString(b.version) + } return repository.UpdateDataSet(ctx, *dataSet.UploadID, update) } diff --git a/data/deduplicator/deduplicator/base_test.go b/data/deduplicator/deduplicator/base_test.go index f0e1499063..326495c3d5 100644 --- a/data/deduplicator/deduplicator/base_test.go +++ b/data/deduplicator/deduplicator/base_test.go @@ -50,13 +50,6 @@ var _ = Describe("Base", func() { Expect(deduplicator).To(BeNil()) }) - It("returns an error when version is invalid", func() { - version = "invalid" - deduplicator, err := dataDeduplicatorDeduplicator.NewBase(name, version) - Expect(err).To(MatchError("version is invalid")) - Expect(deduplicator).To(BeNil()) - }) - It("returns successfully", func() { Expect(dataDeduplicatorDeduplicator.NewBase(name, version)).ToNot(BeNil()) }) @@ -72,6 +65,7 @@ var _ = Describe("Base", func() { Expect(err).ToNot(HaveOccurred()) Expect(deduplicator).ToNot(BeNil()) dataSet = dataTypesUploadTest.RandomUpload() + dataSet.Deduplicator = data.NewDeduplicatorDescriptor() dataSet.Deduplicator.Name = pointer.FromString(name) }) @@ -220,6 +214,7 @@ var _ = Describe("Base", func() { When("the data set has a deduplicator with matching name and version exists", func() { BeforeEach(func() { dataSet.Deduplicator.Version = pointer.FromString(netTest.RandomSemanticVersion()) + update.Deduplicator.Version = dataSet.Deduplicator.Version }) It("returns an error when update data set returns an error", func() { diff --git a/data/deduplicator/deduplicator/data_set_delete_origin_test.go b/data/deduplicator/deduplicator/data_set_delete_origin_test.go index 6691479632..0fff433201 100644 --- a/data/deduplicator/deduplicator/data_set_delete_origin_test.go +++ b/data/deduplicator/deduplicator/data_set_delete_origin_test.go @@ -41,6 +41,7 @@ var _ = Describe("DataSetDeleteOrigin", func() { Expect(err).ToNot(HaveOccurred()) Expect(deduplicator).ToNot(BeNil()) dataSet = dataTypesUploadTest.RandomUpload() + dataSet.Deduplicator = data.NewDeduplicatorDescriptor() dataSet.Deduplicator.Name = pointer.FromString("org.tidepool.deduplicator.dataset.delete.origin") }) @@ -199,6 +200,7 @@ var _ = Describe("DataSetDeleteOrigin", func() { When("the data set has a deduplicator with matching name and version exists", func() { BeforeEach(func() { dataSet.Deduplicator.Version = pointer.FromString(netTest.RandomSemanticVersion()) + update.Deduplicator.Version = dataSet.Deduplicator.Version }) It("returns an error when update data set returns an error", func() { diff --git a/data/deduplicator/deduplicator/device_deactivate_hash.go b/data/deduplicator/deduplicator/device_deactivate_hash.go index f263d34fd7..7d3f8d548c 100644 --- a/data/deduplicator/deduplicator/device_deactivate_hash.go +++ b/data/deduplicator/deduplicator/device_deactivate_hash.go @@ -7,6 +7,13 @@ import ( dataStore "github.com/tidepool-org/platform/data/store" dataTypesUpload "github.com/tidepool-org/platform/data/types/upload" "github.com/tidepool-org/platform/errors" + "github.com/tidepool-org/platform/page" + "github.com/tidepool-org/platform/pointer" +) + +const ( + DeviceDeactivateHashVersionLegacy = "0.0.0" + DeviceDeactivateHashVersionCurrent = "1.1.0" ) const DeviceDeactivateHashName = "org.tidepool.deduplicator.device.deactivate.hash" @@ -18,12 +25,25 @@ var DeviceDeactivateHashDeviceManufacturerDeviceModels = map[string][]string{ "Trividia Health": {"TRUE METRIX", "TRUE METRIX AIR", "TRUE METRIX GO"}, } +var DeviceDeactivateLegacyHashDeviceManufacturerDeviceModels = map[string][]string{ + "Arkray": {"GlucocardExpression"}, + "Bayer": {"Contour Next Link", "Contour Next Link 2.4", "Contour Next", "Contour USB", "Contour Next USB", "Contour Next One", "Contour", "Contour Next EZ", "Contour Plus", "Contour Plus Blue"}, + "Dexcom": {"G5 touchscreen receiver", "G6 touchscreen receiver"}, + "GlucoRx": {"Nexus", "HCT", "Nexus Mini Ultra", "Go"}, + "i-SENS": {"CareSens"}, + "MicroTech": {"Equil"}, + "Roche": {"Aviva Connect", "Performa Connect", "Guide", "Instant (single-button)", "Guide Me", "Instant (two-button)", "Instant S (single-button)", "ReliOn Platinum"}, + + "Insulet": {"Dash", "Eros", "OmniPod"}, + "Tandem": {"1002717", "5602", "5448004", "5448003", "5448001", "5448", "4628003", "4628", "10037177", "1001357", "1000354", "1000096"}, +} + type DeviceDeactivateHash struct { *Base } func NewDeviceDeactivateHash() (*DeviceDeactivateHash, error) { - base, err := NewBase(DeviceDeactivateHashName, "1.1.0") + base, err := NewBase(DeviceDeactivateHashName, DeviceDeactivateHashVersionCurrent) if err != nil { return nil, err } @@ -33,37 +53,48 @@ func NewDeviceDeactivateHash() (*DeviceDeactivateHash, error) { }, nil } +func getDeduplicatorVersion(dataSet *dataTypesUpload.Upload) (string, bool) { + if dataSet.DeviceManufacturers == nil || dataSet.DeviceModel == nil { + return "", false + } + + for _, deviceManufacturer := range *dataSet.DeviceManufacturers { + if allowedDeviceModels, found := DeviceDeactivateLegacyHashDeviceManufacturerDeviceModels[deviceManufacturer]; found { + for _, allowedDeviceModel := range allowedDeviceModels { + if allowedDeviceModel == *dataSet.DeviceModel { + return DeviceDeactivateHashVersionLegacy, true + } + } + } + + if allowedDeviceModels, found := DeviceDeactivateHashDeviceManufacturerDeviceModels[deviceManufacturer]; found { + for _, allowedDeviceModel := range allowedDeviceModels { + if allowedDeviceModel == *dataSet.DeviceModel { + return DeviceDeactivateHashVersionCurrent, true + } + } + } + } + + return "", false +} + func (d *DeviceDeactivateHash) New(ctx context.Context, dataSet *dataTypesUpload.Upload) (bool, error) { if dataSet == nil { return false, errors.New("data set is missing") } - if !dataSet.HasDataSetTypeNormal() { return false, nil } if dataSet.DeviceID == nil { return false, nil } - if dataSet.HasDeduplicatorName() { return d.Get(ctx, dataSet) } - if dataSet.DeviceManufacturers == nil || dataSet.DeviceModel == nil { - return false, nil - } - - for _, deviceManufacturer := range *dataSet.DeviceManufacturers { - if allowedDeviceModels, found := DeviceDeactivateHashDeviceManufacturerDeviceModels[deviceManufacturer]; found { - for _, allowedDeviceModel := range allowedDeviceModels { - if allowedDeviceModel == *dataSet.DeviceModel { - return true, nil - } - } - } - } - - return false, nil + _, found := getDeduplicatorVersion(dataSet) + return found, nil } func (d *DeviceDeactivateHash) Get(ctx context.Context, dataSet *dataTypesUpload.Upload) (bool, error) { @@ -74,6 +105,29 @@ func (d *DeviceDeactivateHash) Get(ctx context.Context, dataSet *dataTypesUpload return dataSet.HasDeduplicatorNameMatch("org.tidepool.hash-deactivate-old"), nil // TODO: DEPRECATED } +func (d *DeviceDeactivateHash) Open(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload) (*dataTypesUpload.Upload, error) { + if ctx == nil { + return nil, errors.New("context is missing") + } + if repository == nil { + return nil, errors.New("repository is missing") + } + if dataSet == nil { + return nil, errors.New("data set is missing") + } + + version, found := getDeduplicatorVersion(dataSet) + if !found { + return nil, errors.New("deduplicator version not found") + } + + dataSet.Deduplicator = data.NewDeduplicatorDescriptor() + dataSet.Deduplicator.Name = pointer.FromString(DeviceDeactivateHashName) + dataSet.Deduplicator.Version = pointer.FromString(version) + + return d.Base.Open(ctx, repository, dataSet) +} + func (d *DeviceDeactivateHash) AddData(ctx context.Context, repository dataStore.DataRepository, dataSet *dataTypesUpload.Upload, dataSetData data.Data) error { if ctx == nil { return errors.New("context is missing") @@ -88,10 +142,26 @@ func (d *DeviceDeactivateHash) AddData(ctx context.Context, repository dataStore return errors.New("data set data is missing") } - if err := AssignDataSetDataIdentityHashes(dataSetData); err != nil { - return err + options := NewDefaultDeviceDeactivateHashOptions() + if *dataSet.Deduplicator.Version == DeviceDeactivateHashVersionLegacy { + filter := &data.DataSetFilter{LegacyOnly: pointer.FromBool(true), DeviceID: dataSet.DeviceID} + pagination := &page.Pagination{Page: 1, Size: 1} + + uploads, err := repository.ListUserDataSets(ctx, *dataSet.UserID, filter, pagination) + if err != nil { + return errors.Wrap(err, "error getting datasets for user") + } + if len(uploads) != 0 { + if uploads[0].LegacyGroupID == nil { + return errors.New("missing required legacy groupId for the device deactivate hash legacy version") + } + options = NewLegacyHashOptions(*uploads[0].LegacyGroupID) + } } + if err := AssignDataSetDataIdentityHashes(dataSetData, options); err != nil { + return err + } return d.Base.AddData(ctx, repository, dataSet, dataSetData) } diff --git a/data/deduplicator/deduplicator/device_deactivate_hash_test.go b/data/deduplicator/deduplicator/device_deactivate_hash_test.go index 9372df4802..c89b00fb26 100644 --- a/data/deduplicator/deduplicator/device_deactivate_hash_test.go +++ b/data/deduplicator/deduplicator/device_deactivate_hash_test.go @@ -25,6 +25,14 @@ var _ = Describe("DeviceDeactivateHash", func() { Expect(dataDeduplicatorDeduplicator.DeviceDeactivateHashName).To(Equal("org.tidepool.deduplicator.device.deactivate.hash")) }) + It("DeviceDeactivateHashVersionCurrent is 1.1.0", func() { + Expect(dataDeduplicatorDeduplicator.DeviceDeactivateHashVersionCurrent).To(Equal("1.1.0")) + }) + + It("DeviceDeactivateHashVersionLegacy is 0.0.0", func() { + Expect(dataDeduplicatorDeduplicator.DeviceDeactivateHashVersionLegacy).To(Equal("0.0.0")) + }) + Context("NewDeviceDeactivateHash", func() { It("returns succesfully", func() { Expect(dataDeduplicatorDeduplicator.NewDeviceDeactivateHash()).ToNot(BeNil()) @@ -64,6 +72,52 @@ var _ = Describe("DeviceDeactivateHash", func() { Expect(deduplicator.New(context.Background(), dataSet)).To(BeFalse()) }) + It("returns false when the deduplicator name does not match", func() { + dataSet.Deduplicator.Name = pointer.FromString(netTest.RandomReverseDomain()) + Expect(deduplicator.New(context.Background(), dataSet)).To(BeFalse()) + }) + + DescribeTable("returns true when", + func(deviceManufacturer string, deviceModel string) { + dataSet.DeviceManufacturers = pointer.FromStringArray([]string{deviceManufacturer}) + dataSet.DeviceModel = pointer.FromString(deviceModel) + Expect(deduplicator.New(context.Background(), dataSet)).To(BeTrue()) + }, + Entry("is Abbott FreeStyle Libre", "Abbott", "FreeStyle Libre"), + Entry("is LifeScan OneTouch Ultra 2", "LifeScan", "OneTouch Ultra 2"), + Entry("is LifeScan OneTouch UltraMini", "LifeScan", "OneTouch UltraMini"), + Entry("is LifeScan Verio", "LifeScan", "Verio"), + Entry("is LifeScan Verio Flex", "LifeScan", "Verio Flex"), + Entry("is Medtronic 523", "Medtronic", "523"), + Entry("is Medtronic 523K", "Medtronic", "523K"), + Entry("is Medtronic 551", "Medtronic", "551"), + Entry("is Medtronic 554", "Medtronic", "554"), + Entry("is Medtronic 723", "Medtronic", "723"), + Entry("is Medtronic 723K", "Medtronic", "723K"), + Entry("is Medtronic 751", "Medtronic", "751"), + Entry("is Medtronic 754", "Medtronic", "754"), + Entry("is Medtronic 1510", "Medtronic", "1510"), + Entry("is Medtronic 1510K", "Medtronic", "1510K"), + Entry("is Medtronic 1511", "Medtronic", "1511"), + Entry("is Medtronic 1512", "Medtronic", "1512"), + Entry("is Medtronic 1580", "Medtronic", "1580"), + Entry("is Medtronic 1581", "Medtronic", "1581"), + Entry("is Medtronic 1582", "Medtronic", "1582"), + Entry("is Medtronic 1710", "Medtronic", "1710"), + Entry("is Medtronic 1710K", "Medtronic", "1710K"), + Entry("is Medtronic 1711", "Medtronic", "1711"), + Entry("is Medtronic 1712", "Medtronic", "1712"), + Entry("is Medtronic 1714", "Medtronic", "1714"), + Entry("is Medtronic 1714K", "Medtronic", "1714K"), + Entry("is Medtronic 1715", "Medtronic", "1715"), + Entry("is Medtronic 1780", "Medtronic", "1780"), + Entry("is Medtronic 1781", "Medtronic", "1781"), + Entry("is Medtronic 1782", "Medtronic", "1782"), + Entry("is Trividia Health TRUE METRIX", "Trividia Health", "TRUE METRIX"), + Entry("is Trividia Health TRUE METRIX AIR", "Trividia Health", "TRUE METRIX AIR"), + Entry("is Trividia Health TRUE METRIX GO", "Trividia Health", "TRUE METRIX GO"), + ) + dataSetTypeAssertions := func() { It("returns false when the deduplicator name does not match", func() { dataSet.Deduplicator.Name = pointer.FromString(netTest.RandomReverseDomain()) @@ -213,6 +267,13 @@ var _ = Describe("DeviceDeactivateHash", func() { dataSet.Deduplicator.Name = pointer.FromString("org.tidepool.hash-deactivate-old") Expect(deduplicator.Get(context.Background(), dataSet)).To(BeTrue()) }) + + It("returns true when the deduplicator name matches and legacy id device and model", func() { + dataSet.Deduplicator.Name = pointer.FromString(dataDeduplicatorDeduplicator.DeviceDeactivateHashName) + dataSet.DeviceManufacturers = pointer.FromStringArray([]string{"Tandem"}) + dataSet.DeviceModel = pointer.FromString("1002717") + Expect(deduplicator.Get(context.Background(), dataSet)).To(BeTrue()) + }) }) Context("with context and repository", func() { @@ -255,7 +316,7 @@ var _ = Describe("DeviceDeactivateHash", func() { update.Active = pointer.FromBool(false) update.Deduplicator = &data.DeduplicatorDescriptor{ Name: pointer.FromString("org.tidepool.deduplicator.device.deactivate.hash"), - Version: pointer.FromString("1.1.0"), + Version: pointer.FromString(dataDeduplicatorDeduplicator.DeviceDeactivateHashVersionCurrent), } }) diff --git a/data/deduplicator/deduplicator/device_truncate_data_set_test.go b/data/deduplicator/deduplicator/device_truncate_data_set_test.go index e9bd9514be..a59797d7d1 100644 --- a/data/deduplicator/deduplicator/device_truncate_data_set_test.go +++ b/data/deduplicator/deduplicator/device_truncate_data_set_test.go @@ -41,6 +41,7 @@ var _ = Describe("DeviceTruncateDataSet", func() { Expect(deduplicator).ToNot(BeNil()) dataSet = dataTypesUploadTest.RandomUpload() dataSet.DataSetType = pointer.FromString("normal") + dataSet.Deduplicator = data.NewDeduplicatorDescriptor() dataSet.Deduplicator.Name = pointer.FromString("org.tidepool.deduplicator.device.truncate.dataset") dataSet.DeviceManufacturers = pointer.FromStringArray([]string{"Animas"}) }) @@ -248,6 +249,7 @@ var _ = Describe("DeviceTruncateDataSet", func() { When("the data set has a deduplicator with matching name and version exists", func() { BeforeEach(func() { dataSet.Deduplicator.Version = pointer.FromString(netTest.RandomSemanticVersion()) + update.Deduplicator.Version = dataSet.Deduplicator.Version }) It("returns an error when update data set returns an error", func() { diff --git a/data/deduplicator/deduplicator/hash.go b/data/deduplicator/deduplicator/hash.go index dc97d91508..82c3780d9f 100644 --- a/data/deduplicator/deduplicator/hash.go +++ b/data/deduplicator/deduplicator/hash.go @@ -1,25 +1,80 @@ package deduplicator import ( + "crypto/sha1" "crypto/sha256" + "encoding/base32" "encoding/base64" + "fmt" "strings" "github.com/tidepool-org/platform/data" + "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/pointer" ) -func AssignDataSetDataIdentityHashes(dataSetData data.Data) error { +type HashOptions struct { + Version int + LegacyGroupID *string +} + +func NewLegacyHashOptions(legacyGroupID string) HashOptions { + return HashOptions{ + Version: types.LegacyIdentityFieldsVersion, + LegacyGroupID: &legacyGroupID, + } +} + +func NewDefaultDeviceDeactivateHashOptions() HashOptions { + return HashOptions{ + Version: types.IdentityFieldsVersion, + } +} + +func (d HashOptions) Validate() error { + + switch d.Version { + case types.LegacyIdentityFieldsVersion: + if d.LegacyGroupID == nil || *d.LegacyGroupID == "" { + return errors.New("missing required legacy groupId for the device deactivate hash legacy version") + } + case types.IdentityFieldsVersion: + if d.LegacyGroupID != nil { + return errors.New("groupId is not required for the device deactivate hash current version") + } + default: + return errors.Newf("missing valid version %d", d.Version) + } + return nil +} + +func AssignDataSetDataIdentityHashes(dataSetData data.Data, options HashOptions) error { + if err := options.Validate(); err != nil { + return err + } for _, dataSetDatum := range dataSetData { - fields, err := dataSetDatum.IdentityFields() + var hash string + + fields, err := dataSetDatum.IdentityFields(options.Version) if err != nil { return errors.Wrap(err, "unable to gather identity fields for datum") } - hash, err := GenerateIdentityHash(fields) - if err != nil { - return errors.Wrap(err, "unable to generate identity hash for datum") + if options.Version == types.LegacyIdentityFieldsVersion { + hash, err = GenerateLegacyIdentityHash(fields) + if err != nil { + return errors.Wrapf(err, "unable to generate legacy identity hash for datum %T", dataSetDatum) + } + hash, err = GenerateLegacyIdentityHash([]string{hash, *options.LegacyGroupID}) + if err != nil { + return errors.Wrapf(err, "unable to generate legacy identity hash with legacy groupID for datum %T", dataSetDatum) + } + } else { + hash, err = GenerateIdentityHash(fields) + if err != nil { + return errors.Wrap(err, "unable to generate identity hash for datum") + } } deduplicator := dataSetDatum.DeduplicatorDescriptor() @@ -44,12 +99,27 @@ func GenerateIdentityHash(identityFields []string) (string, error) { return "", errors.New("identity field is empty") } } - - identityString := strings.Join(identityFields, hashIdentityFieldsSeparator) + identityString := strings.Join(identityFields, "|") identitySum := sha256.Sum256([]byte(identityString)) identityHash := base64.StdEncoding.EncodeToString(identitySum[:]) - return identityHash, nil } -const hashIdentityFieldsSeparator = "|" +func GenerateLegacyIdentityHash(identityFields []string) (string, error) { + if len(identityFields) == 0 { + return "", errors.New("identity fields are missing") + } + hasher := sha1.New() + for _, val := range identityFields { + if val == "" { + return "", errors.New("identity field is empty") + } + hasher.Write([]byte(fmt.Sprintf("%v", val))) + hasher.Write([]byte("_")) + } + + hasher.Write([]byte("bootstrap")) + hasher.Write([]byte("_")) + digest := hasher.Sum(nil) + return base32.NewEncoding("0123456789abcdefghijklmnopqrstuv").WithPadding('-').EncodeToString(digest), nil +} diff --git a/data/deduplicator/deduplicator/hash_test.go b/data/deduplicator/deduplicator/hash_test.go index 4674c17150..af833b2c26 100644 --- a/data/deduplicator/deduplicator/hash_test.go +++ b/data/deduplicator/deduplicator/hash_test.go @@ -6,9 +6,15 @@ import ( "github.com/tidepool-org/platform/data" dataDeduplicatorDeduplicator "github.com/tidepool-org/platform/data/deduplicator/deduplicator" + dataNormalizer "github.com/tidepool-org/platform/data/normalizer" dataTest "github.com/tidepool-org/platform/data/test" + "github.com/tidepool-org/platform/data/types/blood/glucose/selfmonitored" + dataTypesBloodGlucoseTest "github.com/tidepool-org/platform/data/types/blood/glucose/test" "github.com/tidepool-org/platform/errors" + logTest "github.com/tidepool-org/platform/log/test" "github.com/tidepool-org/platform/pointer" + "github.com/tidepool-org/platform/structure" + "github.com/tidepool-org/platform/test" userTest "github.com/tidepool-org/platform/user/test" ) @@ -16,6 +22,8 @@ var _ = Describe("Hash", func() { Context("AssignDataSetDataIdentityHashes", func() { var dataSetDataTest []*dataTest.Datum var dataSetData data.Data + legacyOpts := dataDeduplicatorDeduplicator.NewLegacyHashOptions("legacy-group-id") + defaultOpts := dataDeduplicatorDeduplicator.NewDefaultDeviceDeactivateHashOptions() BeforeEach(func() { dataSetDataTest = []*dataTest.Datum{} @@ -34,35 +42,75 @@ var _ = Describe("Hash", func() { }) It("returns successfully when the data is nil", func() { - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(nil)).To(Succeed()) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(nil, defaultOpts)).To(Succeed()) }) It("returns successfully when there is no data", func() { - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(data.Data{})).To(Succeed()) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(nil, legacyOpts)).To(Succeed()) + }) + + It("returns successfully when there is no data", func() { + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(data.Data{}, defaultOpts)).To(Succeed()) + }) + + It("returns successfully when there is no data", func() { + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(data.Data{}, legacyOpts)).To(Succeed()) }) It("returns an error when any datum returns an error getting identity fields", func() { dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: nil, Error: errors.New("test error")}} - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData)).To(MatchError("unable to gather identity fields for datum; test error")) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, defaultOpts)).To(MatchError("unable to gather identity fields for datum; test error")) + }) + + It("returns an error when any datum returns an error getting legacy identity fields", func() { + dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} + dataSetDataTest[0].GetTypeOutputs = []string{} + dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: nil, Error: errors.New("test error")}} + dataSetDataTest[1].GetTypeOutputs = []string{} + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, legacyOpts)).To(MatchError("unable to gather identity fields for datum; test error")) }) It("returns an error when any datum returns no identity fields", func() { dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: nil, Error: nil}} - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData)).To(MatchError("unable to generate identity hash for datum; identity fields are missing")) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, defaultOpts)).To(MatchError("unable to generate identity hash for datum; identity fields are missing")) + }) + + It("returns an error when any datum returns no legacy identity fields", func() { + dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} + dataSetDataTest[0].GetTypeOutputs = []string{} + dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: nil, Error: nil}} + dataSetDataTest[1].GetTypeOutputs = []string{} + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, legacyOpts)).To(MatchError("unable to generate legacy identity hash for datum *test.Datum; identity fields are missing")) }) It("returns an error when any datum returns empty identity fields", func() { dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{}, Error: nil}} - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData)).To(MatchError("unable to generate identity hash for datum; identity fields are missing")) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, defaultOpts)).To(MatchError("unable to generate identity hash for datum; identity fields are missing")) + }) + + It("returns an error when any datum returns empty legacy identity fields", func() { + dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} + dataSetDataTest[0].GetTypeOutputs = []string{} + dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{}, Error: nil}} + dataSetDataTest[1].GetTypeOutputs = []string{} + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, legacyOpts)).To(MatchError("unable to generate legacy identity hash for datum *test.Datum; identity fields are missing")) }) It("returns an error when any datum returns any empty identity fields", func() { dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), ""}, Error: nil}} - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData)).To(MatchError("unable to generate identity hash for datum; identity field is empty")) + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, defaultOpts)).To(MatchError("unable to generate identity hash for datum; identity field is empty")) + }) + + It("returns an error when any datum returns any empty legacy identity fields", func() { + dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), dataTest.NewDeviceID()}, Error: nil}} + dataSetDataTest[0].GetTypeOutputs = []string{} + dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{userTest.RandomID(), ""}, Error: nil}} + dataSetDataTest[1].GetTypeOutputs = []string{} + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, legacyOpts)).To(MatchError("unable to generate legacy identity hash for datum *test.Datum; identity field is empty")) }) Context("with identity fields", func() { @@ -79,9 +127,59 @@ var _ = Describe("Hash", func() { }) It("returns successfully", func() { - Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData)).To(Succeed()) + + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, defaultOpts)).To(Succeed()) }) }) + + Context("with legacy identity fields", func() { + BeforeEach(func() { + dataSetDataTest[0].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{"test", "0"}, Error: nil}} + dataSetDataTest[0].GetTypeOutputs = []string{} + dataSetDataTest[1].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{"test", "1"}, Error: nil}} + dataSetDataTest[1].GetTypeOutputs = []string{} + dataSetDataTest[2].IdentityFieldsOutputs = []dataTest.IdentityFieldsOutput{{IdentityFields: []string{"test", "2"}, Error: nil}} + dataSetDataTest[2].GetTypeOutputs = []string{} + }) + + AfterEach(func() { + Expect(dataSetDataTest[0].DeduplicatorDescriptorValue).To(Equal(&data.DeduplicatorDescriptor{Hash: pointer.FromString("qspej2rl2vhbui2om3822pb0hh5dvthj")})) + Expect(dataSetDataTest[1].DeduplicatorDescriptorValue).To(Equal(&data.DeduplicatorDescriptor{Hash: pointer.FromString("r75b33k9gqtdo938gnoisu8cgq0rupaf")})) + Expect(dataSetDataTest[2].DeduplicatorDescriptorValue).To(Equal(&data.DeduplicatorDescriptor{Hash: pointer.FromString("r4dlls57b4ro07kufocso8ts46h4h70q")})) + }) + + It("returns successfully", func() { + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(dataSetData, legacyOpts)).To(Succeed()) + }) + }) + + Context("with legacy identity fields", func() { + var smbgData data.Data + BeforeEach(func() { + var newSMBG = func() data.Datum { + datum := selfmonitored.New() + datum.Glucose = *dataTypesBloodGlucoseTest.NewGlucose(pointer.FromString("mg/dl")) + datum.Type = "smbg" + datum.Value = pointer.FromFloat64(150) + datum.SubType = pointer.FromString(test.RandomStringFromArray(selfmonitored.SubTypes())) + + normalizer := dataNormalizer.New(logTest.NewLogger()) + Expect(normalizer).ToNot(BeNil()) + datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) + return datum + } + + for i := 0; i < 10; i++ { + smbgData = append(smbgData, newSMBG()) + } + + }) + + It("returns successfully", func() { + Expect(dataDeduplicatorDeduplicator.AssignDataSetDataIdentityHashes(smbgData, legacyOpts)).To(Succeed()) + }) + + }) }) Context("GenerateIdentityHash", func() { @@ -115,4 +213,43 @@ var _ = Describe("Hash", func() { Expect(dataDeduplicatorDeduplicator.GenerateIdentityHash([]string{"one", "two", "three", "four", "five"})).To(Equal("8HUIFZUXmOuySpngHvl+fJECoeELTiCRxwNxxgDzmVQ=")) }) }) + Context("GenerateLegacyIdentityHash", func() { + It("returns an error when identity fields is missing", func() { + hash, err := dataDeduplicatorDeduplicator.GenerateLegacyIdentityHash(nil) + Expect(err).To(MatchError("identity fields are missing")) + Expect(hash).To(BeEmpty()) + }) + + It("returns an error when identity fields is empty", func() { + hash, err := dataDeduplicatorDeduplicator.GenerateLegacyIdentityHash([]string{}) + Expect(err).To(MatchError("identity fields are missing")) + Expect(hash).To(BeEmpty()) + }) + + It("returns an error when an identity fields empty", func() { + hash, err := dataDeduplicatorDeduplicator.GenerateLegacyIdentityHash([]string{"alpha", "", "charlie"}) + Expect(err).To(MatchError("identity field is empty")) + Expect(hash).To(BeEmpty()) + }) + + DescribeTable("hash from legacy identity tests", + func(fields []string, expectedHash string) { + actualHash, actualErr := dataDeduplicatorDeduplicator.GenerateLegacyIdentityHash(fields) + Expect(actualHash).To(Equal(expectedHash)) + Expect(actualErr).ToNot(HaveOccurred()) + }, + Entry("smbg id", []string{"smbg", "tools", "2014-06-11T11:12:43.029Z", "5.550747991045533"}, "e2ihon9nqcro96c4uugb4ftdnr07nqok"), + Entry("smbg id", []string{"smbg", "tools", "2014-06-11T17:57:01.703Z", "4.5"}, "c14eds071pp5gsirfmgmsclbcahs8th0"), + Entry("smbg id", []string{"smbg", "tools", "2015-07-04T10:13:00.000Z", "4.9"}, "rk2htms97m7hipdu5lrso7ufd3pedm6n"), + Entry("smbg id", []string{"smbg", "tools", "2015-07-04T10:13:00.000Z", "4.8"}, "urrkdln86rl4vhqckps6gnupg5njqk6n"), + Entry("cbg id", []string{"cbg", "tools", "2014-06-11T11:12:43.029Z"}, "eb12p6h892pmd0hhccpt2r17muc407o0"), + Entry("cbg id", []string{"cbg", "tools", "2014-06-11T17:57:01.703Z"}, "ha2ogn1kenqqhseed504sqnanhnclg5s"), + Entry("cbg id", []string{"cbg", "tools", "2014-06-12T11:12:43.029Z"}, "i922lobl3kron3t81pjap31anopkspvb"), + Entry("cbg id", []string{"cbg", "DexHealthKit_Dexcom:com.dexcom.Share2:3.0.4.17", "2015-12-21T11:23:08Z"}, "nsikjhfaprplpq78hc7di2lu5qpt1e3k"), + Entry("basal id", []string{"basal", "scheduled", "tools", "2014-06-11T00:00:00.000Z"}, "kmm427pfbrc6rugtmbuli8j4q61u17uk"), + Entry("basal id", []string{"basal", "scheduled", "tools", "2014-06-11T06:00:00.000Z"}, "cjou7vscvp8ogv34d6vejootulqfn3jd"), + Entry("basal id", []string{"basal", "temp", "tools", "2014-06-11T09:00:00.000Z"}, "tn33bjb0241j9qh4jg9vdnf1g6k1g9r8"), + Entry("basal id", []string{"basal", "scheduled", "tools", "2014-06-11T19:00:00.000Z"}, "kftn188l8rjuvma3qkd3iqg34t0plajp"), + ) + }) }) diff --git a/data/deduplicator/deduplicator/none_test.go b/data/deduplicator/deduplicator/none_test.go index 8c9f7a9808..b74590e18e 100644 --- a/data/deduplicator/deduplicator/none_test.go +++ b/data/deduplicator/deduplicator/none_test.go @@ -45,6 +45,7 @@ var _ = Describe("None", func() { Expect(err).ToNot(HaveOccurred()) Expect(deduplicator).ToNot(BeNil()) dataSet = dataTypesUploadTest.RandomUpload() + dataSet.Deduplicator = data.NewDeduplicatorDescriptor() dataSet.Deduplicator.Name = pointer.FromString("org.tidepool.deduplicator.none") }) @@ -203,6 +204,7 @@ var _ = Describe("None", func() { When("the data set has a deduplicator with matching name and version exists", func() { BeforeEach(func() { dataSet.Deduplicator.Version = pointer.FromString(netTest.RandomSemanticVersion()) + update.Deduplicator.Version = dataSet.Deduplicator.Version }) It("returns an error when update data set returns an error", func() { diff --git a/data/store/mongo/mongo_data_set.go b/data/store/mongo/mongo_data_set.go index 5a3a5db386..b59fb4138e 100644 --- a/data/store/mongo/mongo_data_set.go +++ b/data/store/mongo/mongo_data_set.go @@ -278,6 +278,10 @@ func (d *DataSetRepository) ListUserDataSets(ctx context.Context, userID string, if filter.DeviceID != nil { selector["deviceId"] = *filter.DeviceID } + if filter.LegacyOnly != nil && *filter.LegacyOnly { + selector["_id"] = bson.M{"$not": bson.M{"$type": "objectId"}} + } + opts := storeStructuredMongo.FindWithPagination(pagination). SetSort(bson.M{"createdTime": -1}) cursor, err := d.Find(ctx, selector, opts) diff --git a/data/test/datum.go b/data/test/datum.go index 2e87c0ddf1..a1b49a3173 100644 --- a/data/test/datum.go +++ b/data/test/datum.go @@ -6,6 +6,7 @@ import ( "github.com/onsi/gomega" "github.com/tidepool-org/platform/data" + "github.com/tidepool-org/platform/metadata" "github.com/tidepool-org/platform/origin" "github.com/tidepool-org/platform/structure" @@ -26,6 +27,7 @@ type Datum struct { NormalizeInvocations int NormalizeInputs []data.Normalizer IdentityFieldsInvocations int + IdentityFieldsInputs []int IdentityFieldsOutputs []IdentityFieldsOutput GetPayloadInvocations int GetPayloadOutputs []*metadata.Metadata @@ -102,13 +104,15 @@ func (d *Datum) Normalize(normalizer data.Normalizer) { d.NormalizeInputs = append(d.NormalizeInputs, normalizer) } -func (d *Datum) IdentityFields() ([]string, error) { +func (d *Datum) IdentityFields(version int) ([]string, error) { d.IdentityFieldsInvocations++ + d.IdentityFieldsInputs = append(d.IdentityFieldsInputs, version) gomega.Expect(d.IdentityFieldsOutputs).ToNot(gomega.BeEmpty()) output := d.IdentityFieldsOutputs[0] d.IdentityFieldsOutputs = d.IdentityFieldsOutputs[1:] + return output.IdentityFields, output.Error } diff --git a/data/types/activity/physical/physical.go b/data/types/activity/physical/physical.go index da184eb00e..4bd297dcdc 100644 --- a/data/types/activity/physical/physical.go +++ b/data/types/activity/physical/physical.go @@ -296,3 +296,7 @@ func (p *Physical) Normalize(normalizer data.Normalizer) { p.Step.Normalize(normalizer.WithReference("step")) } } + +func (p *Physical) IdentityFields(version int) ([]string, error) { + return p.Base.IdentityFields(version) +} diff --git a/data/types/activity/physical/physical_test.go b/data/types/activity/physical/physical_test.go index 3dffe18720..4dcf4eaa3b 100644 --- a/data/types/activity/physical/physical_test.go +++ b/data/types/activity/physical/physical_test.go @@ -1,6 +1,8 @@ package physical_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -1363,5 +1365,18 @@ var _ = Describe("Physical", func() { ), ) }) + + Context("Legacy IdentityFields", func() { + It("returns the expected legacy identity fields", func() { + datum := NewPhysical() + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"physicalActivity", "some-device", "2023-05-13T15:51:58.000Z"})) + }) + }) }) }) diff --git a/data/types/basal/basal.go b/data/types/basal/basal.go index 6e5892ce09..f746d764fd 100644 --- a/data/types/basal/basal.go +++ b/data/types/basal/basal.go @@ -2,7 +2,6 @@ package basal import ( "github.com/tidepool-org/platform/data/types" - "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" ) @@ -50,17 +49,40 @@ func (b *Basal) Validate(validator structure.Validator) { validator.String("deliveryType", &b.DeliveryType).Exists().NotEmpty() } -func (b *Basal) IdentityFields() ([]string, error) { - identityFields, err := b.Base.IdentityFields() +func (b *Basal) IdentityFields(version int) ([]string, error) { + identityFields := []string{} + var err error + if version == types.LegacyIdentityFieldsVersion { + + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.Type, "type") + if err != nil { + return nil, err + } + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.DeliveryType, "delivery type") + if err != nil { + return nil, err + } + identityFields, err = types.AppendIdentityFieldVal(identityFields, b.DeviceID, "device id") + if err != nil { + return nil, err + } + identityFields, err = types.AppendLegacyTimeVal(identityFields, b.Time) + if err != nil { + return nil, err + } + return identityFields, nil + } + + identityFields, err = b.Base.IdentityFields(version) if err != nil { return nil, err } - - if b.DeliveryType == "" { - return nil, errors.New("delivery type is empty") + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.DeliveryType, "delivery type") + if err != nil { + return nil, err } - return append(identityFields, b.DeliveryType), nil + return identityFields, nil } func ParseDeliveryType(parser structure.ObjectParser) *string { diff --git a/data/types/basal/basal_test.go b/data/types/basal/basal_test.go index 5ce9f0ec12..6239412453 100644 --- a/data/types/basal/basal_test.go +++ b/data/types/basal/basal_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/gomega" "github.com/tidepool-org/platform/data" + "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/basal" dataTypesBasalTest "github.com/tidepool-org/platform/data/types/basal/test" dataTypesTest "github.com/tidepool-org/platform/data/types/test" @@ -103,31 +104,56 @@ var _ = Describe("Basal", func() { It("returns error if user id is missing", func() { datumBasal.UserID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("user id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if user id is empty", func() { datumBasal.UserID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("user id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if delivery type is empty", func() { datumBasal.DeliveryType = "" - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("delivery type is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns the expected identity fields", func() { - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).ToNot(HaveOccurred()) Expect(identityFields).To(Equal([]string{*datumBasal.UserID, *datumBasal.DeviceID, (*datumBasal.Time).Format(ExpectedTimeFormat), datumBasal.Type, datumBasal.DeliveryType})) }) }) + Context("Legacy IdentityFields", func() { + var datum *basal.Basal + + BeforeEach(func() { + datum = dataTypesBasalTest.RandomBasal() + }) + + It("returns error if delivery type is empty", func() { + datum.DeliveryType = "" + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("delivery type is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns the expected legacy identity fields", func() { + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + datum.DeliveryType = "some-delivery" + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"basal", "some-delivery", "some-device", "2023-05-13T15:51:58.000Z"})) + }) + }) }) Context("ParseDeliveryType", func() { diff --git a/data/types/basal/suspend/suspend.go b/data/types/basal/suspend/suspend.go index f87e742ece..82bcf5c2fb 100644 --- a/data/types/basal/suspend/suspend.go +++ b/data/types/basal/suspend/suspend.go @@ -13,7 +13,6 @@ import ( const ( DeliveryType = "suspend" // TODO: Rename Type to "basal/suspended"; remove DeliveryType - DurationMaximum = 604800000 DurationMinimum = 0 ) @@ -60,12 +59,12 @@ func (s *Suspend) Validate(validator structure.Validator) { validator.String("deliveryType", &s.DeliveryType).EqualTo(DeliveryType) } - validator.Int("duration", s.Duration).Exists().InRange(DurationMinimum, DurationMaximum) + validator.Int("duration", s.Duration).Exists().GreaterThanOrEqualTo(DurationMinimum) expectedDurationValidator := validator.Int("expectedDuration", s.DurationExpected) - if s.Duration != nil && *s.Duration >= DurationMinimum && *s.Duration <= DurationMaximum { - expectedDurationValidator.InRange(*s.Duration, DurationMaximum) + if s.Duration != nil && *s.Duration >= DurationMinimum { + expectedDurationValidator.GreaterThanOrEqualTo(*s.Duration) } else { - expectedDurationValidator.InRange(DurationMinimum, DurationMaximum) + expectedDurationValidator.GreaterThanOrEqualTo(DurationMinimum) } validateSuppressed(validator.WithReference("suppressed"), s.Suppressed) } diff --git a/data/types/basal/suspend/suspend_test.go b/data/types/basal/suspend/suspend_test.go index 60c4062543..9b0c8feb05 100644 --- a/data/types/basal/suspend/suspend_test.go +++ b/data/types/basal/suspend/suspend_test.go @@ -1,6 +1,8 @@ package suspend_test import ( + "math" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -34,8 +36,8 @@ func NewSuspend() *suspend.Suspend { datum := suspend.New() datum.Basal = *dataTypesBasalTest.RandomBasal() datum.DeliveryType = "suspend" - datum.Duration = pointer.FromInt(test.RandomIntFromRange(suspend.DurationMinimum, suspend.DurationMaximum)) - datum.DurationExpected = pointer.FromInt(test.RandomIntFromRange(*datum.Duration, suspend.DurationMaximum)) + datum.Duration = pointer.FromInt(test.RandomIntFromRange(suspend.DurationMinimum, math.MaxInt32)) + datum.DurationExpected = pointer.FromInt(test.RandomIntFromRange(*datum.Duration, math.MaxInt32)) datum.Suppressed = dataTypesBasalTemporaryTest.RandomSuppressedTemporary(dataTypesBasalScheduledTest.RandomSuppressedScheduled()) return datum } @@ -66,10 +68,6 @@ var _ = Describe("Suspend", func() { Expect(suspend.DeliveryType).To(Equal("suspend")) }) - It("DurationMaximum is expected", func() { - Expect(suspend.DurationMaximum).To(Equal(604800000)) - }) - It("DurationMinimum is expected", func() { Expect(suspend.DurationMinimum).To(Equal(0)) }) @@ -136,7 +134,7 @@ var _ = Describe("Suspend", func() { datum.DurationExpected = pointer.FromInt(-1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/expectedDuration", NewMeta()), ), Entry("duration missing; duration expected in range (lower)", func(datum *suspend.Suspend) { @@ -145,57 +143,48 @@ var _ = Describe("Suspend", func() { }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), - Entry("duration missing; duration expected in range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(604800000) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - ), - Entry("duration missing; duration expected out of range (upper)", + Entry("duration missing", func(datum *suspend.Suspend) { datum.Duration = nil datum.DurationExpected = pointer.FromInt(604800001) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/expectedDuration", NewMeta()), ), Entry("duration out of range (lower); duration expected missing", func(datum *suspend.Suspend) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/duration", NewMeta()), ), Entry("duration out of range (lower); duration expected out of range (lower)", func(datum *suspend.Suspend) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/expectedDuration", NewMeta()), ), Entry("duration out of range (lower); duration expected in range (lower)", func(datum *suspend.Suspend) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/duration", NewMeta()), ), Entry("duration out of range (lower); duration expected in range (upper)", func(datum *suspend.Suspend) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(604800000) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/duration", NewMeta()), ), - Entry("duration out of range (lower); duration expected out of range (upper)", + Entry("duration out of range (lower); duration expected in range (upper)", func(datum *suspend.Suspend) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(604800001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/duration", NewMeta()), ), Entry("duration in range (lower); duration expected missing", func(datum *suspend.Suspend) { @@ -208,7 +197,7 @@ var _ = Describe("Suspend", func() { datum.Duration = pointer.FromInt(0) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThanOrEqualTo(-1, 0), "/expectedDuration", NewMeta()), ), Entry("duration in range (lower); duration expected in range (lower)", func(datum *suspend.Suspend) { @@ -216,88 +205,6 @@ var _ = Describe("Suspend", func() { datum.DurationExpected = pointer.FromInt(0) }, ), - Entry("duration in range (lower); duration expected in range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(604800000) - }, - ), - Entry("duration in range (lower); duration expected out of range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(604800001) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/expectedDuration", NewMeta()), - ), - Entry("duration in range (upper); duration expected missing", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800000) - datum.DurationExpected = nil - }, - ), - Entry("duration in range (upper); duration expected out of range (lower)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800000) - datum.DurationExpected = pointer.FromInt(604799999) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604799999, 604800000, 604800000), "/expectedDuration", NewMeta()), - ), - Entry("duration in range (upper); duration expected in range (lower)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800000) - datum.DurationExpected = pointer.FromInt(604800000) - }, - ), - Entry("duration in range (upper); duration expected in range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800000) - datum.DurationExpected = pointer.FromInt(604800000) - }, - ), - Entry("duration in range (upper); duration expected out of range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800000) - datum.DurationExpected = pointer.FromInt(604800001) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 604800000, 604800000), "/expectedDuration", NewMeta()), - ), - Entry("duration out of range (upper); duration expected missing", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800001) - datum.DurationExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration", NewMeta()), - ), - Entry("duration out of range (upper); duration expected out of range (lower)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800001) - datum.DurationExpected = pointer.FromInt(-1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), - ), - Entry("duration out of range (upper); duration expected in range (lower)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800001) - datum.DurationExpected = pointer.FromInt(0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration", NewMeta()), - ), - Entry("duration out of range (upper); duration expected in range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800001) - datum.DurationExpected = pointer.FromInt(604800000) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration", NewMeta()), - ), - Entry("duration out of range (upper); duration expected out of range (upper)", - func(datum *suspend.Suspend) { - datum.Duration = pointer.FromInt(604800001) - datum.DurationExpected = pointer.FromInt(604800001) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/expectedDuration", NewMeta()), - ), Entry("suppressed missing", func(datum *suspend.Suspend) { datum.Suppressed = nil }, ), @@ -343,7 +250,6 @@ var _ = Describe("Suspend", func() { errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "basal"), "/type", &basal.Meta{Type: "invalidType", DeliveryType: "invalidDeliveryType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidDeliveryType", "suspend"), "/deliveryType", &basal.Meta{Type: "invalidType", DeliveryType: "invalidDeliveryType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", &basal.Meta{Type: "invalidType", DeliveryType: "invalidDeliveryType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/expectedDuration", &basal.Meta{Type: "invalidType", DeliveryType: "invalidDeliveryType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotValid(), "/suppressed/suppressed", &basal.Meta{Type: "invalidType", DeliveryType: "invalidDeliveryType"}), ), ) diff --git a/data/types/base.go b/data/types/base.go index 26e63e7d71..20b555ccf8 100644 --- a/data/types/base.go +++ b/data/types/base.go @@ -17,17 +17,20 @@ import ( ) const ( - ClockDriftOffsetMaximum = 24 * 60 * 60 * 1000 // TODO: Fix! Limit to reasonable values - ClockDriftOffsetMinimum = -24 * 60 * 60 * 1000 // TODO: Fix! Limit to reasonable values - DeviceTimeFormat = "2006-01-02T15:04:05" - NoteLengthMaximum = 1000 - NotesLengthMaximum = 100 - TagLengthMaximum = 100 - TagsLengthMaximum = 100 - TimeFormat = time.RFC3339Nano - TimeZoneOffsetMaximum = 7 * 24 * 60 // TODO: Fix! Limit to reasonable values - TimeZoneOffsetMinimum = -7 * 24 * 60 // TODO: Fix! Limit to reasonable values - VersionInternalMinimum = 0 + ClockDriftOffsetMaximum = 24 * 60 * 60 * 1000 // TODO: Fix! Limit to reasonable values + ClockDriftOffsetMinimum = -24 * 60 * 60 * 1000 // TODO: Fix! Limit to reasonable values + DeviceTimeFormat = "2006-01-02T15:04:05" + LegacyTimeFormat = "2006-01-02T15:04:05.000Z" + NoteLengthMaximum = 1000 + NotesLengthMaximum = 100 + TagLengthMaximum = 100 + TagsLengthMaximum = 100 + TimeFormat = time.RFC3339Nano + TimeZoneOffsetMaximum = 7 * 24 * 60 // TODO: Fix! Limit to reasonable values + TimeZoneOffsetMinimum = -7 * 24 * 60 // TODO: Fix! Limit to reasonable values + VersionInternalMinimum = 0 + LegacyIdentityFieldsVersion = 0 + IdentityFieldsVersion = 1 ) type Base struct { @@ -237,30 +240,80 @@ func (b *Base) Normalize(normalizer data.Normalizer) { } } -func (b *Base) IdentityFields() ([]string, error) { - if b.UserID == nil { - return nil, errors.New("user id is missing") - } - if *b.UserID == "" { - return nil, errors.New("user id is empty") - } - if b.DeviceID == nil { - return nil, errors.New("device id is missing") +func currentIdentityFields(b *Base) ([]string, error) { + + vals := []string{} + var err error + vals, err = AppendIdentityFieldVal(vals, b.UserID, "user id") + if err != nil { + return nil, err } - if *b.DeviceID == "" { - return nil, errors.New("device id is empty") + + vals, err = AppendIdentityFieldVal(vals, b.DeviceID, "device id") + if err != nil { + return nil, err } + if b.Time == nil { return nil, errors.New("time is missing") } if (*b.Time).IsZero() { return nil, errors.New("time is empty") } - if b.Type == "" { - return nil, errors.New("type is empty") + vals = append(vals, (*b.Time).Format(TimeFormat)) + + vals, err = AppendIdentityFieldVal(vals, &b.Type, "type") + if err != nil { + return nil, err + } + + return vals, nil +} + +func (b *Base) IdentityFields(version int) ([]string, error) { + if version == LegacyIdentityFieldsVersion { + return legacyIdentityFields(b) + } + return currentIdentityFields(b) +} + +func AppendLegacyTimeVal(vals []string, t *time.Time) ([]string, error) { + if t == nil { + return nil, errors.New("time is missing") + } + if (*t).IsZero() { + return nil, errors.New("time is empty") + } + return append(vals, (*t).Format(LegacyTimeFormat)), nil +} + +func AppendIdentityFieldVal(vals []string, val *string, errDetail string) ([]string, error) { + if val == nil { + return nil, errors.Newf("%s is missing", errDetail) + } + if *val == "" { + return nil, errors.Newf("%s is empty", errDetail) } + vals = append(vals, *val) + return vals, nil +} - return []string{*b.UserID, *b.DeviceID, (*b.Time).Format(TimeFormat), b.Type}, nil +func legacyIdentityFields(b *Base) ([]string, error) { + vals := []string{} + var err error + vals, err = AppendIdentityFieldVal(vals, &b.Type, "type") + if err != nil { + return nil, err + } + vals, err = AppendIdentityFieldVal(vals, b.DeviceID, "device id") + if err != nil { + return nil, err + } + vals, err = AppendLegacyTimeVal(vals, b.Time) + if err != nil { + return nil, err + } + return vals, nil } func (b *Base) GetOrigin() *origin.Origin { diff --git a/data/types/base_test.go b/data/types/base_test.go index ee63d4035f..1c2e837635 100644 --- a/data/types/base_test.go +++ b/data/types/base_test.go @@ -33,6 +33,14 @@ import ( const ExpectedTimeFormat = time.RFC3339Nano var _ = Describe("Base", func() { + Context("constants", func() { + It("legacy time format", func() { + Expect(types.LegacyTimeFormat).To(Equal("2006-01-02T15:04:05.000Z")) + }) + It("device time format", func() { + Expect(types.DeviceTimeFormat).To(Equal("2006-01-02T15:04:05")) + }) + }) Context("New", func() { It("creates a new datum with all values initialized", func() { typ := dataTypesTest.NewType() @@ -904,6 +912,7 @@ var _ = Describe("Base", func() { }) Context("with new, initialized datum", func() { + const currentVersion = types.IdentityFieldsVersion var datumBase *types.Base var datum data.Datum @@ -915,60 +924,87 @@ var _ = Describe("Base", func() { Context("IdentityFields", func() { It("returns error if user id is missing", func() { datumBase.UserID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if user id is empty", func() { datumBase.UserID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if device id is missing", func() { datumBase.DeviceID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("device id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if device id is empty", func() { datumBase.DeviceID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("device id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if time is missing", func() { datumBase.Time = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("time is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if time is empty", func() { datumBase.Time = pointer.FromTime(time.Time{}) - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("time is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if type is empty", func() { datumBase.Type = "" - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("type is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns the expected identity fields", func() { - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).ToNot(HaveOccurred()) Expect(identityFields).To(Equal([]string{*datumBase.UserID, *datumBase.DeviceID, (*datumBase.Time).Format(ExpectedTimeFormat), datumBase.Type})) }) }) + Context("Legacy IdentityFields", func() { + var datum *types.Base + + BeforeEach(func() { + datum = dataTypesTest.RandomBase() + }) + + It("returns the expected empty identity fields", func() { + legacyIDFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(BeNil()) + Expect(legacyIDFields).ToNot(BeEmpty()) + Expect(legacyIDFields).To(Equal([]string{datum.Type, *datum.DeviceID, (*datum.Time).Format(types.LegacyTimeFormat)})) + }) + + Context("AppendLegacyTimeVal", func() { + It("returns expected time in legacy time format", func() { + t, _ := time.Parse(time.RFC3339Nano, "2015-07-31T23:59:59.999Z") + vals := []string{} + var err error + vals, err = types.AppendLegacyTimeVal(vals, &t) + Expect(err).To(BeNil()) + Expect(len(vals)).To(Equal(1)) + Expect(vals).To(ContainElement("2015-07-31T23:59:59.999Z")) + }) + }) + }) + Context("GetPayload", func() { It("gets the payload", func() { Expect(datum.GetPayload()).To(Equal(datumBase.Payload)) diff --git a/data/types/blood/blood.go b/data/types/blood/blood.go index 174c252ffc..656160d2f6 100644 --- a/data/types/blood/blood.go +++ b/data/types/blood/blood.go @@ -5,14 +5,16 @@ import ( "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/errors" + "github.com/tidepool-org/platform/metadata" "github.com/tidepool-org/platform/structure" ) type Blood struct { types.Base `bson:",inline"` - Units *string `json:"units,omitempty" bson:"units,omitempty"` - Value *float64 `json:"value,omitempty" bson:"value,omitempty"` + Units *string `json:"units,omitempty" bson:"units,omitempty"` + Value *float64 `json:"value,omitempty" bson:"value,omitempty"` + Raw *metadata.Metadata `json:"raw,omitempty" bson:"raw,omitempty"` } func New(typ string) Blood { @@ -28,18 +30,66 @@ func (b *Blood) Parse(parser structure.ObjectParser) { b.Value = parser.Float64("value") } -func (b *Blood) IdentityFields() ([]string, error) { - identityFields, err := b.Base.IdentityFields() +func (b *Blood) IdentityFields(version int) ([]string, error) { + if version == types.LegacyIdentityFieldsVersion { + return b.Base.IdentityFields(version) + } + identityFields, err := b.Base.IdentityFields(version) if err != nil { return nil, err } - if b.Units == nil { return nil, errors.New("units is missing") } if b.Value == nil { return nil, errors.New("value is missing") } - return append(identityFields, *b.Units, strconv.FormatFloat(*b.Value, 'f', -1, 64)), nil } + +func (b *Blood) GetRawValueAndUnits() (*float64, *string, error) { + if b.Raw == nil { + return nil, nil, errors.New("raw data is missing") + } + + rUnits := b.Raw.Get("units") + if rUnits == nil { + return nil, nil, errors.New("raw units are missing") + } + + units, ok := rUnits.(string) + if !ok { + return nil, nil, errors.New("raw units are invalid") + } + + rValue := b.Raw.Get("value") + if rValue == nil { + return nil, nil, errors.New("raw value is missing") + } + + value, ok := rValue.(float64) + if !ok { + return nil, nil, errors.New("raw value is invalid") + } + + return &value, &units, nil + +} + +func (b *Blood) SetRawValueAndUnits(value *float64, units *string) { + if b.Raw == nil { + b.Raw = metadata.NewMetadata() + } + + if units != nil { + b.Raw.Set("units", *units) + } else { + b.Raw.Delete("units") + } + + if value != nil { + b.Raw.Set("value", *value) + } else { + b.Raw.Delete("value") + } +} diff --git a/data/types/blood/blood_test.go b/data/types/blood/blood_test.go index c74a565050..20aee08774 100644 --- a/data/types/blood/blood_test.go +++ b/data/types/blood/blood_test.go @@ -89,6 +89,7 @@ var _ = Describe("Blood", func() { }) Context("IdentityFields", func() { + const currentVersion = types.IdentityFieldsVersion var datumBlood *blood.Blood var datum data.Datum @@ -99,37 +100,52 @@ var _ = Describe("Blood", func() { It("returns error if user id is missing", func() { datumBlood.UserID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if user id is empty", func() { datumBlood.UserID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if units is missing", func() { datumBlood.Units = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("units is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if value is missing", func() { datumBlood.Value = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("value is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns the expected identity fields", func() { - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).ToNot(HaveOccurred()) Expect(identityFields).To(Equal([]string{*datumBlood.UserID, *datumBlood.DeviceID, (*datumBlood.Time).Format(ExpectedTimeFormat), datumBlood.Type, *datumBlood.Units, strconv.FormatFloat(*datumBlood.Value, 'f', -1, 64)})) }) }) + + Context("Legacy IdentityFields", func() { + It("returns the expected legacy identity fields", func() { + datum := dataTypesBloodTest.NewBlood() + datum.Type = "bg" + datum.DeviceID = pointer.FromString("some-bg-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"bg", "some-bg-device", "2023-05-13T15:51:58.000Z"})) + }) + }) + }) }) diff --git a/data/types/blood/glucose/continuous/continuous_test.go b/data/types/blood/glucose/continuous/continuous_test.go index 558534a85f..7cd5035e49 100644 --- a/data/types/blood/glucose/continuous/continuous_test.go +++ b/data/types/blood/glucose/continuous/continuous_test.go @@ -12,6 +12,8 @@ import ( dataTypesTest "github.com/tidepool-org/platform/data/types/test" errorsTest "github.com/tidepool-org/platform/errors/test" logTest "github.com/tidepool-org/platform/log/test" + "github.com/tidepool-org/platform/metadata" + metadataTest "github.com/tidepool-org/platform/metadata/test" "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" @@ -266,6 +268,7 @@ var _ = Describe("Continuous", func() { Context("Normalize", func() { DescribeTable("normalizes the datum", func(units *string, mutator func(datum *continuous.Continuous, units *string), expectator func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string)) { + for _, origin := range structure.Origins() { datum := NewContinuous(units) mutator(datum, units) @@ -275,6 +278,7 @@ var _ = Describe("Continuous", func() { datum.Normalize(normalizer.WithOrigin(origin)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { expectator(datum, expectedDatum, units) } @@ -314,17 +318,19 @@ var _ = Describe("Continuous", func() { ) DescribeTable("normalizes the datum with origin external", - func(units *string, mutator func(datum *continuous.Continuous, units *string), expectator func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string)) { + func(units *string, mutator func(datum *continuous.Continuous, units *string), expectator func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64)) { datum := NewContinuous(units) mutator(datum, units) + originalValue := pointer.CloneFloat64(datum.Value) expectedDatum := CloneContinuous(datum) normalizer := dataNormalizer.New(logTest.NewLogger()) Expect(normalizer).ToNot(BeNil()) datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { - expectator(datum, expectedDatum, units) + expectator(datum, expectedDatum, units, originalValue) } Expect(datum).To(Equal(expectedDatum)) }, @@ -346,45 +352,51 @@ var _ = Describe("Continuous", func() { Entry("modifies the datum; units mmol/l", pointer.FromString("mmol/l"), func(datum *continuous.Continuous, units *string) {}, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mmol/l; value missing", pointer.FromString("mmol/l"), func(datum *continuous.Continuous, units *string) { datum.Value = nil }, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), Entry("modifies the datum; units mg/dL", pointer.FromString("mg/dL"), func(datum *continuous.Continuous, units *string) {}, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dL; value missing", pointer.FromString("mg/dL"), func(datum *continuous.Continuous, units *string) { datum.Value = nil }, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), Entry("modifies the datum; units mg/dl", pointer.FromString("mg/dl"), func(datum *continuous.Continuous, units *string) {}, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dl; value missing", pointer.FromString("mg/dl"), func(datum *continuous.Continuous, units *string) { datum.Value = nil }, - func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string) { + func(datum *continuous.Continuous, expectedDatum *continuous.Continuous, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), ) diff --git a/data/types/blood/glucose/glucose.go b/data/types/blood/glucose/glucose.go index 422db011c7..6dc6935c16 100644 --- a/data/types/blood/glucose/glucose.go +++ b/data/types/blood/glucose/glucose.go @@ -28,8 +28,8 @@ func (g *Glucose) Normalize(normalizer data.Normalizer) { g.Blood.Normalize(normalizer) if normalizer.Origin() == structure.OriginExternal { - units := g.Units - g.Units = dataBloodGlucose.NormalizeUnits(units) - g.Value = dataBloodGlucose.NormalizeValueForUnits(g.Value, units) + g.SetRawValueAndUnits(g.Value, g.Units) + g.Value = dataBloodGlucose.NormalizeValueForUnits(g.Value, g.Units) + g.Units = dataBloodGlucose.NormalizeUnits(g.Units) } } diff --git a/data/types/blood/glucose/glucose_test.go b/data/types/blood/glucose/glucose_test.go index c343f6ce9f..4f4367aca1 100644 --- a/data/types/blood/glucose/glucose_test.go +++ b/data/types/blood/glucose/glucose_test.go @@ -13,6 +13,8 @@ import ( dataTypesTest "github.com/tidepool-org/platform/data/types/test" errorsTest "github.com/tidepool-org/platform/errors/test" logTest "github.com/tidepool-org/platform/log/test" + "github.com/tidepool-org/platform/metadata" + metadataTest "github.com/tidepool-org/platform/metadata/test" "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" @@ -286,6 +288,7 @@ var _ = Describe("Glucose", func() { Context("Normalize", func() { DescribeTable("normalizes the datum", func(units *string, mutator func(datum *glucose.Glucose, units *string), expectator func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string)) { + for _, origin := range structure.Origins() { datum := dataTypesBloodGlucoseTest.NewGlucose(units) mutator(datum, units) @@ -295,6 +298,7 @@ var _ = Describe("Glucose", func() { datum.Normalize(normalizer.WithOrigin(origin)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { expectator(datum, expectedDatum, units) } @@ -324,17 +328,19 @@ var _ = Describe("Glucose", func() { ) DescribeTable("normalizes the datum with origin external", - func(units *string, mutator func(datum *glucose.Glucose, units *string), expectator func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string)) { + func(units *string, mutator func(datum *glucose.Glucose, units *string), expectator func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64)) { datum := dataTypesBloodGlucoseTest.NewGlucose(units) mutator(datum, units) + originalValue := pointer.CloneFloat64(datum.Value) expectedDatum := dataTypesBloodGlucoseTest.CloneGlucose(datum) normalizer := dataNormalizer.New(logTest.NewLogger()) Expect(normalizer).ToNot(BeNil()) datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { - expectator(datum, expectedDatum, units) + expectator(datum, expectedDatum, units, originalValue) } Expect(datum).To(Equal(expectedDatum)) }, @@ -361,15 +367,17 @@ var _ = Describe("Glucose", func() { Entry("modifies the datum; units mmol/l", pointer.FromString("mmol/l"), func(datum *glucose.Glucose, units *string) {}, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mmol/l; value missing", pointer.FromString("mmol/l"), func(datum *glucose.Glucose, units *string) { datum.Value = nil }, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), Entry("modifies the datum; units mg/dL", @@ -377,16 +385,18 @@ var _ = Describe("Glucose", func() { func(datum *glucose.Glucose, units *string) { datum.Value = pointer.FromFloat64(test.RandomFloat64FromRange(dataBloodGlucose.ValueRangeForUnits(units))) }, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dL; value missing", pointer.FromString("mg/dL"), func(datum *glucose.Glucose, units *string) { datum.Value = nil }, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), Entry("modifies the datum; units mg/dl", @@ -394,16 +404,18 @@ var _ = Describe("Glucose", func() { func(datum *glucose.Glucose, units *string) { datum.Value = pointer.FromFloat64(test.RandomFloat64FromRange(dataBloodGlucose.ValueRangeForUnits(units))) }, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dl; value missing", pointer.FromString("mg/dl"), func(datum *glucose.Glucose, units *string) { datum.Value = nil }, - func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string) { + func(datum *glucose.Glucose, expectedDatum *glucose.Glucose, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), ) diff --git a/data/types/blood/glucose/selfmonitored/selfmonitored.go b/data/types/blood/glucose/selfmonitored/selfmonitored.go index 303c7ef4e2..0f8a0a5dca 100644 --- a/data/types/blood/glucose/selfmonitored/selfmonitored.go +++ b/data/types/blood/glucose/selfmonitored/selfmonitored.go @@ -1,7 +1,11 @@ package selfmonitored import ( + "strconv" + "github.com/tidepool-org/platform/data" + dataBloodGlucose "github.com/tidepool-org/platform/data/blood/glucose" + "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/blood/glucose" "github.com/tidepool-org/platform/structure" ) @@ -65,3 +69,20 @@ func (s *SelfMonitored) Normalize(normalizer data.Normalizer) { s.Glucose.Normalize(normalizer) } + +func (s *SelfMonitored) IdentityFields(version int) ([]string, error) { + if version == types.LegacyIdentityFieldsVersion { + identityFields, err := s.Blood.IdentityFields(version) + if err != nil { + return nil, err + } + value, units, err := s.GetRawValueAndUnits() + if err != nil { + return nil, err + } + fullPrecisionValue := dataBloodGlucose.NormalizeValueForUnitsWithFullPrecision(value, units) + identityFields = append(identityFields, strconv.FormatFloat(*fullPrecisionValue, 'f', -1, 64)) + return identityFields, nil + } + return s.Blood.IdentityFields(version) +} diff --git a/data/types/blood/glucose/selfmonitored/selfmonitored_test.go b/data/types/blood/glucose/selfmonitored/selfmonitored_test.go index c16948a4a3..e19171f876 100644 --- a/data/types/blood/glucose/selfmonitored/selfmonitored_test.go +++ b/data/types/blood/glucose/selfmonitored/selfmonitored_test.go @@ -1,6 +1,8 @@ package selfmonitored_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -12,6 +14,8 @@ import ( dataTypesTest "github.com/tidepool-org/platform/data/types/test" errorsTest "github.com/tidepool-org/platform/errors/test" logTest "github.com/tidepool-org/platform/log/test" + "github.com/tidepool-org/platform/metadata" + metadataTest "github.com/tidepool-org/platform/metadata/test" "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" @@ -32,6 +36,12 @@ func NewSelfMonitored(units *string) *selfmonitored.SelfMonitored { return datum } +func NewSelfMonitoredWithValue(units *string, val *float64) *selfmonitored.SelfMonitored { + datum := NewSelfMonitored(units) + datum.Value = pointer.FromFloat64(*val) + return datum +} + func CloneSelfMonitored(datum *selfmonitored.SelfMonitored) *selfmonitored.SelfMonitored { if datum == nil { return nil @@ -71,6 +81,7 @@ var _ = Describe("SelfMonitored", func() { Expect(datum.Units).To(BeNil()) Expect(datum.Value).To(BeNil()) Expect(datum.SubType).To(BeNil()) + Expect(datum.Raw).To(BeNil()) }) }) @@ -296,6 +307,7 @@ var _ = Describe("SelfMonitored", func() { datum.Normalize(normalizer.WithOrigin(origin)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { expectator(datum, expectedDatum, units) } @@ -350,8 +362,9 @@ var _ = Describe("SelfMonitored", func() { ) DescribeTable("normalizes the datum with origin external", - func(units *string, mutator func(datum *selfmonitored.SelfMonitored, units *string), expectator func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string)) { + func(units *string, mutator func(datum *selfmonitored.SelfMonitored, units *string), expectator func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64)) { datum := NewSelfMonitored(units) + originalValue := pointer.CloneFloat64(datum.Value) mutator(datum, units) expectedDatum := CloneSelfMonitored(datum) normalizer := dataNormalizer.New(logTest.NewLogger()) @@ -359,8 +372,9 @@ var _ = Describe("SelfMonitored", func() { datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) Expect(normalizer.Error()).To(BeNil()) Expect(normalizer.Data()).To(BeEmpty()) + expectedDatum.Raw = metadataTest.CloneMetadata(datum.Raw) if expectator != nil { - expectator(datum, expectedDatum, units) + expectator(datum, expectedDatum, units, originalValue) } Expect(datum).To(Equal(expectedDatum)) }, @@ -377,45 +391,52 @@ var _ = Describe("SelfMonitored", func() { Entry("modifies the datum; units mmol/l", pointer.FromString("mmol/l"), func(datum *selfmonitored.SelfMonitored, units *string) {}, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mmol/l; value missing", pointer.FromString("mmol/l"), func(datum *selfmonitored.SelfMonitored, units *string) { datum.Value = nil }, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units}) }, ), Entry("modifies the datum; units mg/dL", pointer.FromString("mg/dL"), func(datum *selfmonitored.SelfMonitored, units *string) {}, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dL; value missing", pointer.FromString("mg/dL"), func(datum *selfmonitored.SelfMonitored, units *string) { datum.Value = nil }, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, expectedDatum.Raw) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units}) }, ), Entry("modifies the datum; units mg/dl", pointer.FromString("mg/dl"), func(datum *selfmonitored.SelfMonitored, units *string) {}, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) dataBloodGlucoseTest.ExpectNormalizedValue(datum.Value, expectedDatum.Value, units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": *value}) }, ), Entry("modifies the datum; units mg/dl; value missing", pointer.FromString("mg/dl"), func(datum *selfmonitored.SelfMonitored, units *string) { datum.Value = nil }, - func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string) { + func(datum *selfmonitored.SelfMonitored, expectedDatum *selfmonitored.SelfMonitored, units *string, value *float64) { dataBloodGlucoseTest.ExpectNormalizedUnits(datum.Units, expectedDatum.Units) + dataBloodGlucoseTest.ExpectRaw(datum.Raw, &metadata.Metadata{"units": *units, "value": nil}) }, ), ) @@ -479,5 +500,79 @@ var _ = Describe("SelfMonitored", func() { ), ) }) + + Context("Legacy IdentityFields", func() { + var datum *selfmonitored.SelfMonitored + + BeforeEach(func() { + datum = NewSelfMonitoredWithValue(pointer.FromString("mg/dl"), pointer.FromFloat64(144)) + normalizer := dataNormalizer.New(logTest.NewLogger()) + Expect(normalizer).ToNot(BeNil()) + datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) + }) + + It("returns error if device id is missing", func() { + datum.DeviceID = nil + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("device id is missing")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if device id is empty", func() { + datum.DeviceID = pointer.FromString("") + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("device id is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if time is missing", func() { + datum.Time = nil + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("time is missing")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if time is empty", func() { + datum.Time = &time.Time{} + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("time is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if raw is missing", func() { + datum.Raw = nil + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err.Error()).To(Equal("raw data is missing")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if raw value is missing", func() { + datum.Raw.Delete("value") + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err.Error()).To(Equal("raw value is missing")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns error if raw units are missing", func() { + datum.Raw.Delete("units") + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err.Error()).To(Equal("raw units are missing")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns the expected legacy identity fields", func() { + datum = NewSelfMonitoredWithValue(pointer.FromString("mg/dl"), pointer.FromFloat64(220)) + normalizer := dataNormalizer.New(logTest.NewLogger()) + Expect(normalizer).ToNot(BeNil()) + datum.Normalize(normalizer.WithOrigin(structure.OriginExternal)) + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"smbg", "some-device", "2023-05-13T15:51:58.000Z", "12.211645580300173"})) + }) + }) }) }) diff --git a/data/types/blood/test/blood.go b/data/types/blood/test/blood.go index 048b20ec54..601c1779b8 100644 --- a/data/types/blood/test/blood.go +++ b/data/types/blood/test/blood.go @@ -5,6 +5,8 @@ import ( "github.com/tidepool-org/platform/data/types/blood" dataTypesTest "github.com/tidepool-org/platform/data/types/test" + "github.com/tidepool-org/platform/metadata" + metadataTest "github.com/tidepool-org/platform/metadata/test" "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/test" ) @@ -14,6 +16,7 @@ func NewBlood() *blood.Blood { datum.Base = *dataTypesTest.RandomBase() datum.Units = pointer.FromString(dataTypesTest.NewType()) datum.Value = pointer.FromFloat64(test.RandomFloat64FromRange(-math.MaxFloat64, math.MaxFloat64)) + datum.Raw = &metadata.Metadata{"units": datum.Units, "value": datum.Value} return datum } @@ -25,5 +28,6 @@ func CloneBlood(datum *blood.Blood) *blood.Blood { clone.Base = *dataTypesTest.CloneBase(&datum.Base) clone.Units = pointer.CloneString(datum.Units) clone.Value = pointer.CloneFloat64(datum.Value) + clone.Raw = metadataTest.CloneMetadata(datum.Raw) return clone } diff --git a/data/types/bolus/automated/automated.go b/data/types/bolus/automated/automated.go index ebca2c562b..43f5b9b76b 100644 --- a/data/types/bolus/automated/automated.go +++ b/data/types/bolus/automated/automated.go @@ -9,7 +9,7 @@ import ( const ( SubType = "automated" // TODO: Rename Type to "bolus/automated"; remove SubType - NormalMaximum = 100.0 + NormalMaximum = 250.0 NormalMinimum = 0.0 ) diff --git a/data/types/bolus/automated/automated_test.go b/data/types/bolus/automated/automated_test.go index 522031429c..f8eab99817 100644 --- a/data/types/bolus/automated/automated_test.go +++ b/data/types/bolus/automated/automated_test.go @@ -31,7 +31,7 @@ var _ = Describe("Automated", func() { }) It("NormalMaximum is expected", func() { - Expect(dataTypesBolusAutomated.NormalMaximum).To(Equal(100.0)) + Expect(dataTypesBolusAutomated.NormalMaximum).To(Equal(250.0)) }) It("NormalMinimum is expected", func() { @@ -144,7 +144,7 @@ var _ = Describe("Automated", func() { datum.NormalExpected = pointer.FromFloat64(-0.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal missing; normal expected in range (lower)", func(datum *dataTypesBolusAutomated.Automated) { @@ -156,54 +156,54 @@ var _ = Describe("Automated", func() { Entry("normal missing; normal expected in range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), ), Entry("normal missing; normal expected out of range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (lower); normal expected missing", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected out of range (lower)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (lower); normal expected in range (lower)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected in range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected out of range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (lower); normal expected missing", func(datum *dataTypesBolusAutomated.Automated) { @@ -217,7 +217,7 @@ var _ = Describe("Automated", func() { datum.Normal = pointer.FromFloat64(0.0) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (lower); normal expected in range (lower)", func(datum *dataTypesBolusAutomated.Automated) { @@ -228,96 +228,96 @@ var _ = Describe("Automated", func() { Entry("normal in range (lower); normal expected in range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, ), Entry("normal in range (lower); normal expected out of range (upper)", func(datum *dataTypesBolusAutomated.Automated) { datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (upper); normal expected missing", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) datum.NormalExpected = nil }, ), Entry("normal in range (upper); normal expected out of range (lower)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(99.9) + datum.Normal = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(249.9) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(99.9, 100.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(249.9, dataTypesBolusAutomated.NormalMaximum, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (upper); normal expected in range (lower)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, ), Entry("normal in range (upper); normal expected in range (upper)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, ), Entry("normal in range (upper); normal expected out of range (upper)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 100.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, dataTypesBolusAutomated.NormalMaximum, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (upper); normal expected missing", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected out of range (lower)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (upper); normal expected in range (lower)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected in range (upper)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(dataTypesBolusAutomated.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected out of range (upper)", func(datum *dataTypesBolusAutomated.Automated) { - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("multiple errors", func(datum *dataTypesBolusAutomated.Automated) { datum.Type = "invalidType" datum.SubType = "invalidSubType" datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &dataTypesBolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", "automated"), "/subType", &dataTypesBolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", &dataTypesBolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", &dataTypesBolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(dataTypesBolusAutomated.NormalMaximum+0.1, 0.0, dataTypesBolusAutomated.NormalMaximum), "/expectedNormal", &dataTypesBolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), ), ) }) diff --git a/data/types/bolus/bolus.go b/data/types/bolus/bolus.go index fe969bb338..1521f7187f 100644 --- a/data/types/bolus/bolus.go +++ b/data/types/bolus/bolus.go @@ -4,12 +4,16 @@ import ( "github.com/tidepool-org/platform/data" "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/insulin" - "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/structure" ) const ( Type = "bolus" + + DeliveryContextDevice = "device" + DeliveryContextAlgorithm = "algorithm" + DeliveryContextRemote = "remote" + DeliveryContextUndetermined = "undetermined" ) type Bolus struct { @@ -17,6 +21,7 @@ type Bolus struct { SubType string `json:"subType,omitempty" bson:"subType,omitempty"` + DeliveryContext *string `json:"deliveryContext,omitempty" bson:"deliveryContext,omitempty"` InsulinFormulation *insulin.Formulation `json:"insulinFormulation,omitempty" bson:"insulinFormulation,omitempty"` } @@ -25,6 +30,10 @@ type Meta struct { SubType string `json:"subType,omitempty"` } +func DeliveryContexts() []string { + return []string{DeliveryContextDevice, DeliveryContextAlgorithm, DeliveryContextRemote, DeliveryContextUndetermined} +} + func New(subType string) Bolus { return Bolus{ Base: types.New(Type), @@ -41,7 +50,7 @@ func (b *Bolus) Meta() interface{} { func (b *Bolus) Parse(parser structure.ObjectParser) { b.Base.Parse(parser) - + b.DeliveryContext = parser.String("deliveryContext") b.InsulinFormulation = insulin.ParseFormulation(parser.WithReferenceObjectParser("insulinFormulation")) } @@ -57,6 +66,8 @@ func (b *Bolus) Validate(validator structure.Validator) { if b.InsulinFormulation != nil { b.InsulinFormulation.Validate(validator.WithReference("insulinFormulation")) } + + validator.String("deliveryContext", b.DeliveryContext).OneOf(DeliveryContexts()...) } func (b *Bolus) Normalize(normalizer data.Normalizer) { @@ -67,15 +78,43 @@ func (b *Bolus) Normalize(normalizer data.Normalizer) { } } -func (b *Bolus) IdentityFields() ([]string, error) { - identityFields, err := b.Base.IdentityFields() +func (b *Bolus) IdentityFields(version int) ([]string, error) { + + identityFields := []string{} + var err error + if version == types.LegacyIdentityFieldsVersion { + + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.Type, "type") + if err != nil { + return nil, err + } + + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.SubType, "sub type") + if err != nil { + return nil, err + } + + identityFields, err = types.AppendIdentityFieldVal(identityFields, b.DeviceID, "device id") + if err != nil { + return nil, err + } + + identityFields, err = types.AppendLegacyTimeVal(identityFields, b.Time) + if err != nil { + return nil, err + } + return identityFields, nil + } + + identityFields, err = b.Base.IdentityFields(version) if err != nil { return nil, err } - if b.SubType == "" { - return nil, errors.New("sub type is empty") + identityFields, err = types.AppendIdentityFieldVal(identityFields, &b.SubType, "sub type") + if err != nil { + return nil, err } - return append(identityFields, b.SubType), nil + return identityFields, nil } diff --git a/data/types/bolus/bolus_test.go b/data/types/bolus/bolus_test.go index ceddc61feb..21c998c168 100644 --- a/data/types/bolus/bolus_test.go +++ b/data/types/bolus/bolus_test.go @@ -8,6 +8,7 @@ import ( "github.com/tidepool-org/platform/data" dataNormalizer "github.com/tidepool-org/platform/data/normalizer" + "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/bolus" dataTypesBolusTest "github.com/tidepool-org/platform/data/types/bolus/test" dataTypesInsulinTest "github.com/tidepool-org/platform/data/types/insulin/test" @@ -33,6 +34,7 @@ var _ = Describe("Bolus", func() { Expect(datum.Type).To(Equal("bolus")) Expect(datum.SubType).To(Equal(subType)) Expect(datum.InsulinFormulation).To(BeNil()) + Expect(datum.DeliveryContext).To(BeNil()) }) }) @@ -85,6 +87,16 @@ var _ = Describe("Bolus", func() { Entry("sub type valid", func(datum *bolus.Bolus) { datum.SubType = dataTypesTest.NewType() }, ), + Entry("delivery context missing", + func(datum *bolus.Bolus) { datum.DeliveryContext = nil }, + ), + Entry("delivery context invalid", + func(datum *bolus.Bolus) { datum.DeliveryContext = pointer.FromString("invalid") }, + errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", bolus.DeliveryContexts()), "/deliveryContext"), + ), + Entry("delivery context valid", + func(datum *bolus.Bolus) { datum.DeliveryContext = pointer.FromString(bolus.DeliveryContextAlgorithm) }, + ), Entry("insulin formulation missing", func(datum *bolus.Bolus) { datum.InsulinFormulation = nil }, ), @@ -155,30 +167,56 @@ var _ = Describe("Bolus", func() { It("returns error if user id is missing", func() { datumBolus.UserID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("user id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if user id is empty", func() { datumBolus.UserID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("user id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if sub type is empty", func() { datumBolus.SubType = "" - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).To(MatchError("sub type is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns the expected identity fields", func() { - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(types.IdentityFieldsVersion) Expect(err).ToNot(HaveOccurred()) Expect(identityFields).To(Equal([]string{*datumBolus.UserID, *datumBolus.DeviceID, (*datumBolus.Time).Format(ExpectedTimeFormat), datumBolus.Type, datumBolus.SubType})) }) }) + + Context("Legacy IdentityFields", func() { + var datum *bolus.Bolus + + BeforeEach(func() { + datum = dataTypesBolusTest.RandomBolus() + }) + + It("returns error if sub type is empty", func() { + datum.SubType = "" + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("sub type is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns the expected legacy identity fields", func() { + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + datum.SubType = "some-sub-type" + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"bolus", "some-sub-type", "some-device", "2023-05-13T15:51:58.000Z"})) + }) + }) }) }) diff --git a/data/types/bolus/combination/combination.go b/data/types/bolus/combination/combination.go index e670253179..ec140fb452 100644 --- a/data/types/bolus/combination/combination.go +++ b/data/types/bolus/combination/combination.go @@ -11,9 +11,9 @@ const ( DurationMaximum = 86400000 DurationMinimum = 0 - ExtendedMaximum = 100.0 + ExtendedMaximum = 250.0 ExtendedMinimum = 0.0 - NormalMaximum = 100.0 + NormalMaximum = 250.0 NormalMinimum = 0.0 ) @@ -60,67 +60,28 @@ func (c *Combination) Validate(validator structure.Validator) { validator.String("subType", &c.SubType).EqualTo(SubType) } - if c.NormalExpected != nil { - validator.Int("duration", c.Duration).Exists().EqualTo(DurationMinimum) - validator.Int("expectedDuration", c.DurationExpected).Exists().InRange(DurationMinimum, DurationMaximum) - validator.Float64("extended", c.Extended).Exists().EqualTo(ExtendedMinimum) - extendedExpectedValidator := validator.Float64("expectedExtended", c.ExtendedExpected) - extendedExpectedValidator.InRange(ExtendedMinimum, ExtendedMaximum) - if c.Normal != nil { - if *c.Normal == NormalMinimum { - if c.ExtendedExpected == nil { - validator.Float64("expectedNormal", c.NormalExpected).GreaterThan(NormalMinimum) - } - extendedExpectedValidator.GreaterThan(ExtendedMinimum) - } else { - extendedExpectedValidator.Exists() - } - } + validator.Int("duration", c.Duration).Exists().InRange(DurationMinimum, DurationMaximum) + durationExpectedValidator := validator.Int("expectedDuration", c.DurationExpected) + if c.Duration != nil && *c.Duration >= DurationMinimum && *c.Duration <= DurationMaximum { + durationExpectedValidator.InRange(*c.Duration, DurationMaximum) + } else { + durationExpectedValidator.InRange(DurationMinimum, DurationMaximum) + } + + validator.Float64("extended", c.Extended).Exists().InRange(ExtendedMinimum, ExtendedMaximum) + extendedExpectedValidator := validator.Float64("expectedExtended", c.ExtendedExpected) + if c.Extended != nil && *c.Extended >= ExtendedMinimum && *c.Extended <= ExtendedMaximum { + extendedExpectedValidator.InRange(*c.Extended, ExtendedMaximum) } else { - validator.Int("duration", c.Duration).Exists().InRange(DurationMinimum, DurationMaximum) - expectedDurationValidator := validator.Int("expectedDuration", c.DurationExpected) - if c.Duration != nil && *c.Duration >= DurationMinimum && *c.Duration <= DurationMaximum { - expectedDurationValidator.InRange(*c.Duration, DurationMaximum) - } else { - expectedDurationValidator.InRange(DurationMinimum, DurationMaximum) - } - if c.ExtendedExpected != nil { - expectedDurationValidator.Exists() - } else { - expectedDurationValidator.NotExists() - } - validator.Float64("extended", c.Extended).Exists().InRange(ExtendedMinimum, ExtendedMaximum) - expectedExtendedValidator := validator.Float64("expectedExtended", c.ExtendedExpected) - if c.Extended != nil && *c.Extended >= ExtendedMinimum && *c.Extended <= ExtendedMaximum { - if *c.Extended == ExtendedMinimum { - if c.Normal != nil && *c.Normal == NormalMinimum { - expectedExtendedValidator.GreaterThan(ExtendedMinimum) - } - expectedExtendedValidator.Exists() - } - expectedExtendedValidator.InRange(*c.Extended, ExtendedMaximum) - } else { - expectedExtendedValidator.InRange(ExtendedMinimum, ExtendedMaximum) - } + extendedExpectedValidator.InRange(ExtendedMinimum, ExtendedMaximum) } + validator.Float64("normal", c.Normal).Exists().InRange(NormalMinimum, NormalMaximum) - expectedNormalValidator := validator.Float64("expectedNormal", c.NormalExpected) + normalExpectedValidator := validator.Float64("expectedNormal", c.NormalExpected) if c.Normal != nil && *c.Normal >= NormalMinimum && *c.Normal <= NormalMaximum { - if *c.Normal == NormalMinimum { - // If Normal is zero, then _either_: - if c.Extended != nil { - if c.NormalExpected == nil { - validator.Float64("extended", c.Extended).GreaterThanOrEqualTo(ExtendedMinimum) - } else { - validator.Float64("extended", c.Extended).Exists() - } - } else { - expectedNormalValidator.GreaterThan(NormalMinimum) - } - } - expectedNormalValidator.InRange(*c.Normal, NormalMaximum) + normalExpectedValidator.InRange(*c.Normal, NormalMaximum) } else { - expectedNormalValidator.InRange(NormalMinimum, NormalMaximum) + normalExpectedValidator.InRange(NormalMinimum, NormalMaximum) } } diff --git a/data/types/bolus/combination/combination_test.go b/data/types/bolus/combination/combination_test.go index 7109258021..555e9c39c7 100644 --- a/data/types/bolus/combination/combination_test.go +++ b/data/types/bolus/combination/combination_test.go @@ -11,7 +11,6 @@ import ( "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" - "github.com/tidepool-org/platform/test" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -38,7 +37,7 @@ var _ = Describe("Combination", func() { }) It("ExtendedMaximum is expected", func() { - Expect(combination.ExtendedMaximum).To(Equal(100.0)) + Expect(combination.ExtendedMaximum).To(Equal(250.0)) }) It("ExtendedMinimum is expected", func() { @@ -46,7 +45,7 @@ var _ = Describe("Combination", func() { }) It("NormalMaximum is expected", func() { - Expect(combination.NormalMaximum).To(Equal(100.0)) + Expect(combination.NormalMaximum).To(Equal(250.0)) }) It("NormalMinimum is expected", func() { @@ -85,11 +84,11 @@ var _ = Describe("Combination", func() { ), Entry("type missing", func(datum *combination.Combination) { datum.Type = "" }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueEmpty(), "/type", &bolus.Meta{SubType: "dual/square"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueEmpty(), "/type", &bolus.Meta{SubType: combination.SubType}), ), Entry("type invalid", func(datum *combination.Combination) { datum.Type = "invalidType" }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &bolus.Meta{Type: "invalidType", SubType: "dual/square"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &bolus.Meta{Type: "invalidType", SubType: combination.SubType}), ), Entry("type bolus", func(datum *combination.Combination) { datum.Type = "bolus" }, @@ -100,1069 +99,651 @@ var _ = Describe("Combination", func() { ), Entry("sub type invalid", func(datum *combination.Combination) { datum.SubType = "invalidSubType" }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", "dual/square"), "/subType", &bolus.Meta{Type: "bolus", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", combination.SubType), "/subType", &bolus.Meta{Type: "bolus", SubType: "invalidSubType"}), ), Entry("sub type dual/square", - func(datum *combination.Combination) { datum.SubType = "dual/square" }, + func(datum *combination.Combination) { datum.SubType = combination.SubType }, ), - Entry("normal expected missing; duration missing; duration expected missing", + Entry("normal missing; normal expected missing", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = nil + datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), ), - Entry("normal expected missing; duration missing; duration expected out of range (lower)", + Entry("normal missing; normal expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(-1) + datum.Normal = nil + datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration missing; duration expected in range (lower)", + Entry("normal missing; normal expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(0) + datum.Normal = nil + datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), ), - Entry("normal expected missing; duration missing; duration expected in range (upper)", + Entry("normal missing; normal expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = nil + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), ), - Entry("normal expected missing; duration missing; duration expected out of range (upper)", + Entry("normal missing; normal expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(86400001) + datum.Normal = nil + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration out of range (lower); duration expected missing", + Entry("normal out of range (lower); normal expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = pointer.FromFloat64(-0.1) + datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), ), - Entry("normal expected missing; duration out of range (lower); duration expected out of range (lower)", + Entry("normal out of range (lower); normal expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(-1) + datum.Normal = pointer.FromFloat64(-0.1) + datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration out of range (lower); duration expected in range (lower)", + Entry("normal out of range (lower); normal expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(0) + datum.Normal = pointer.FromFloat64(-0.1) + datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), ), - Entry("normal expected missing; duration out of range (lower); duration expected in range (upper)", + Entry("normal out of range (lower); normal expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(-0.1) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), ), - Entry("normal expected missing; duration out of range (lower); duration expected out of range (upper)", + Entry("normal out of range (lower); normal expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(-0.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration in range (lower); duration expected missing", + Entry("normal in range (lower); normal expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = pointer.FromFloat64(0.0) + datum.NormalExpected = pointer.FromFloat64(-0.1) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration in range (lower); duration expected out of range (lower)", + Entry("normal in range (lower); normal expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(-1) + datum.Normal = pointer.FromFloat64(0.0) + datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; duration in range (lower); duration expected in range (lower)", + Entry("normal in range (lower); normal expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(0) + datum.Normal = pointer.FromFloat64(0.0) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) }, ), - Entry("normal expected missing; duration in range (lower); duration expected in range (upper)", + Entry("normal in range (lower); normal expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(0.0) + datum.NormalExpected = pointer.FromFloat64(250.1) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration in range (lower); duration expected out of range (upper)", + Entry("normal in range (upper); normal expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; duration in range (upper); duration expected missing", + Entry("normal in range (upper); normal expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(249.9) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(249.9, combination.NormalMaximum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration in range (upper); duration expected out of range (lower)", + Entry("normal in range (upper); normal expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86399999) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86399999, 86400000, 86400000), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; duration in range (upper); duration expected in range (lower)", + Entry("normal in range (upper); normal expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) }, ), - Entry("normal expected missing; duration in range (upper); duration expected in range (upper)", + Entry("normal in range (upper); normal expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(250.1) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMaximum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration in range (upper); duration expected out of range (upper)", + Entry("normal out of range (upper); normal expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 86400000, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), ), - Entry("normal expected missing; duration out of range (upper); duration expected missing", + Entry("normal out of range (upper); normal expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400001) - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), ), - Entry("normal expected missing; duration out of range (upper); duration expected out of range (lower)", + Entry("normal out of range (upper); normal expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(0.0) + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + ), + Entry("normal out of range (upper); normal expected in range (upper)", + func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + ), + Entry("normal out of range (upper); normal expected out of range (upper)", + func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(250.1) + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", NewMeta()), + ), + Entry("duration missing; duration expected out of range (lower)", + func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = nil datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; duration out of range (upper); duration expected in range (lower)", + Entry("duration missing; duration expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = nil datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), - Entry("normal expected missing; duration out of range (upper); duration expected in range (upper)", + Entry("duration missing; duration expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400001) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = nil + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), - Entry("normal expected missing; duration out of range (upper); duration expected out of range (upper)", + Entry("duration missing; duration expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = nil datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended missing; extended expected missing", + Entry("duration out of range (lower); duration expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(-1) datum.DurationExpected = nil - datum.Extended = nil datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended missing; extended expected out of range (lower)", + Entry("duration out of range (lower); duration expected out of range (lower)", func(datum *combination.Combination) { - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(-0.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(-1) + datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended missing; extended expected in range (lower)", + Entry("duration out of range (lower); duration expected in range (lower)", func(datum *combination.Combination) { - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(0.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(-1) + datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended missing; extended expected in range (upper)", + Entry("duration out of range (lower); duration expected in range (upper)", func(datum *combination.Combination) { - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(-1) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended missing; extended expected out of range (upper)", + Entry("duration out of range (lower); duration expected out of range (upper)", func(datum *combination.Combination) { - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(-1) + datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended out of range (lower); extended expected missing", + Entry("duration in range (lower); duration expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(0) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(-0.1) datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (lower); extended expected out of range (lower)", + Entry("duration in range (lower); duration expected out of range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(-0.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(0) + datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended out of range (lower); extended expected in range (lower)", + Entry("duration in range (lower); duration expected in range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(0.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(0) + datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (lower); extended expected in range (upper)", + Entry("duration in range (lower); duration expected in range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(0) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (lower); extended expected out of range (upper)", + Entry("duration in range (lower); duration expected out of range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(0) + datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended in range (lower); extended expected missing", + Entry("duration in range (upper); duration expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(combination.DurationMaximum) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), ), - Entry("normal expected missing; extended in range (lower); extended expected out of range (lower)", + Entry("duration in range (upper); duration expected out of range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(-0.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(combination.DurationMaximum) + datum.DurationExpected = pointer.FromInt(604799999) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604799999, combination.DurationMaximum, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended in range (lower); extended expected in range (lower)", + Entry("duration in range (upper); duration expected in range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(0.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(combination.DurationMaximum) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, ), - Entry("normal expected missing; extended in range (lower); extended expected in range (upper)", + Entry("duration in range (upper); duration expected in range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(combination.DurationMaximum) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, ), - Entry("normal expected missing; extended in range (lower); extended expected out of range (upper)", + Entry("duration in range (upper); duration expected out of range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(combination.DurationMaximum) + datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, combination.DurationMaximum, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended in range (upper); extended expected missing", + Entry("duration out of range (upper); duration expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(86400001) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(100.0) datum.ExtendedExpected = nil }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended in range (upper); extended expected out of range (lower)", + Entry("duration out of range (upper); duration expected out of range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(99.9) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(86400001) + datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(99.9, 100.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended in range (upper); extended expected in range (lower)", + Entry("duration out of range (upper); duration expected in range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(86400001) + datum.DurationExpected = pointer.FromInt(0) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended in range (upper); extended expected in range (upper)", + Entry("duration out of range (upper); duration expected in range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(86400001) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/duration", NewMeta()), ), - Entry("normal expected missing; extended in range (upper); extended expected out of range (upper)", + Entry("duration out of range (upper); duration expected out of range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Duration = pointer.FromInt(86400001) + datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 100.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/expectedDuration", NewMeta()), ), - Entry("normal expected missing; extended out of range (upper); extended expected missing", + Entry("extended missing; extended expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(100.1) + datum.Extended = nil datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (upper); extended expected out of range (lower)", + Entry("extended missing; extended expected out of range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = nil datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected missing; extended out of range (upper); extended expected in range (lower)", + Entry("extended missing; extended expected in range (lower)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = nil datum.ExtendedExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (upper); extended expected in range (upper)", + Entry("extended missing; extended expected in range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = nil + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), ), - Entry("normal expected missing; extended out of range (upper); extended expected out of range (upper)", + Entry("extended missing; extended expected out of range (upper)", func(datum *combination.Combination) { - datum.Extended = pointer.FromFloat64(100.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = nil + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected missing; duration missing; extended expected missing", + Entry("extended out of range (lower); extended expected missing", func(datum *combination.Combination) { + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.DurationExpected = nil + datum.Extended = pointer.FromFloat64(-0.1) datum.ExtendedExpected = nil - datum.NormalExpected = nil }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected missing; duration missing; extended expected exists", + Entry("extended out of range (lower); extended expected out of range (lower)", func(datum *combination.Combination) { - datum.DurationExpected = nil - datum.NormalExpected = nil + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(-0.1) + datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected missing; duration exists; extended expected missing", + Entry("extended out of range (lower); extended expected in range (lower)", func(datum *combination.Combination) { - datum.ExtendedExpected = nil - datum.NormalExpected = nil + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(-0.1) + datum.ExtendedExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueExists(), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected missing; duration exists; extended expected exists", + Entry("extended out of range (lower); extended expected in range (upper)", func(datum *combination.Combination) { - datum.DurationExpected = nil - datum.ExtendedExpected = nil + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(-0.1) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected exists; duration missing; duration expected missing", + Entry("extended out of range (lower); extended expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(-0.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration missing; duration expected out of range (lower)", + Entry("extended in range (lower); extended expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(-1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration missing; duration expected in range (lower)", + Entry("extended in range (lower); extended expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.ExtendedExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), - Entry("normal expected exists; duration missing; duration expected in range (upper)", + Entry("extended in range (lower); extended expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(86400000) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), - Entry("normal expected exists; duration missing; duration expected out of range (upper)", + Entry("extended in range (lower); extended expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = nil - datum.DurationExpected = pointer.FromInt(86400001) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration out of range (lower); duration expected missing", + Entry("extended in range (upper); extended expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Extended = pointer.FromFloat64(combination.ExtendedMaximum) + datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), ), - Entry("normal expected exists; duration out of range (lower); duration expected out of range (lower)", + Entry("extended in range (upper); extended expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(-1) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(combination.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(249.9) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(249.9, combination.ExtendedMaximum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration out of range (lower); duration expected in range (lower)", + Entry("extended in range (upper); extended expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(combination.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-1, 0), "/duration", NewMeta()), ), - Entry("normal expected exists; duration out of range (lower); duration expected in range (upper)", + Entry("extended in range (upper); extended expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(86400000) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(combination.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-1, 0), "/duration", NewMeta()), ), - Entry("normal expected exists; duration out of range (lower); duration expected out of range (upper)", + Entry("extended in range (upper); extended expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(86400001) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(combination.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMaximum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration in range; duration expected missing", + Entry("extended out of range (upper); extended expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected exists; duration in range; duration expected out of range (lower)", + Entry("extended out of range (upper); extended expected out of range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(-1) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration in range; duration expected in range (lower)", + Entry("extended out of range (upper); extended expected in range (lower)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(0.0) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected exists; duration in range; duration expected in range (upper)", + Entry("extended out of range (upper); extended expected in range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(86400000) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), ), - Entry("normal expected exists; duration in range; duration expected out of range (upper)", + Entry("extended out of range (upper); extended expected out of range (upper)", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(86400001) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", NewMeta()), ), - Entry("normal expected exists; duration out of range (upper); duration expected missing", + Entry("duration missing; extended expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(1) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), - ), - Entry("normal expected exists; duration out of range (upper); duration expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(1) - datum.DurationExpected = pointer.FromInt(-1) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), - ), - Entry("normal expected exists; duration out of range (upper); duration expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(1) - datum.DurationExpected = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(1, 0), "/duration", NewMeta()), ), - Entry("normal expected exists; duration out of range (upper); duration expected in range (upper)", + Entry("duration missing; extended expected exists", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(1) - datum.DurationExpected = pointer.FromInt(86400000) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.DurationExpected = nil + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(1, 0), "/duration", NewMeta()), ), - Entry("normal expected exists; duration out of range (upper); duration expected out of range (upper)", + Entry("duration exists; extended expected missing", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(1) - datum.DurationExpected = pointer.FromInt(86400001) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) + datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(1, 0), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), ), - Entry("normal expected exists; extended missing; extended expected missing", + Entry("duration exists; extended expected exists", func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.ExtendedExpected = nil - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended missing; extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended missing; extended expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended missing; extended expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended missing; extended expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (lower); extended expected missing", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = nil - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (lower); extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (lower); extended expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-0.1, 0.0), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (lower); extended expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-0.1, 0.0), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (lower); extended expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(-0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended in range; extended expected missing", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = nil - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended in range; extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended in range; extended expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - ), - Entry("normal expected exists; extended in range; extended expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - ), - Entry("normal expected exists; extended in range; extended expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (upper); extended expected missing", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.1) - datum.ExtendedExpected = nil - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (upper); extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.1) - datum.ExtendedExpected = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (upper); extended expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.1) - datum.ExtendedExpected = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(0.1, 0.0), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (upper); extended expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(0.1, 0.0), "/extended", NewMeta()), - ), - Entry("normal expected exists; extended out of range (upper); extended expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(test.RandomFloat64FromRange(*datum.Normal, combination.NormalMaximum)) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo(0.1, 0.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("normal in range (lower); extended in range (lower); normal expected in range (lower); extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThan(0.0, 0.0), "/expectedExtended", NewMeta()), - ), - Entry("normal missing; normal expected missing", - func(datum *combination.Combination) { - datum.Normal = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - ), - Entry("normal missing; normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(-0.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal missing; normal expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - ), - Entry("normal missing; normal expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - ), - Entry("normal missing; normal expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal out of range (lower); normal expected missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (lower); normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(-0.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal out of range (lower); normal expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (lower); normal expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (lower); normal expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (lower); normal expected missing, extended missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(0.0) - datum.Extended = nil - datum.NormalExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - ), - Entry("normal in range (lower); extended in range (lower); extended expected missing, normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(0.0) - datum.ExtendedExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThan(0.0, 0.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (lower); extended in range (lower); extended expected missing, extended expected out of range (lower)", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(0.0) - datum.Extended = pointer.FromFloat64(0.0) - datum.NormalExpected = nil - datum.ExtendedExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThan(0.0, 0.0), "/expectedExtended", NewMeta()), - ), - Entry("normal in range (lower); normal expected in range, extended missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(0.0) - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.NormalExpected = pointer.FromFloat64(1.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - ), - Entry("normal in range (lower); normal expected out of range, extended missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(0.0) - datum.Duration = pointer.FromInt(0) - datum.Extended = nil - datum.NormalExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotGreaterThan(0.0, 0.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (lower); normal expected missing, extended in range", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Normal = pointer.FromFloat64(0.0) - datum.Extended = pointer.FromFloat64(1.0) - datum.NormalExpected = nil - }, - ), - Entry("normal in range (lower); normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(-0.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (lower); normal expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(0.0) - }, - ), - Entry("normal in range (lower); normal expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - ), - Entry("normal in range (lower); normal expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (upper); normal expected missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = nil - }, - ), - Entry("normal in range (upper); normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(99.9) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(99.9, 100.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (upper); normal expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - ), - Entry("normal in range (upper); normal expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - ), - Entry("normal in range (upper); normal expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 100.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal out of range (upper); normal expected missing", - func(datum *combination.Combination) { - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (upper); normal expected out of range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(-0.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal out of range (upper); normal expected in range (lower)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(0.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (upper); normal expected in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.0) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - ), - Entry("normal out of range (upper); normal expected out of range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.1) - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("allow normal and extended to be 0 when extended expected is missing", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.0) - datum.ExtendedExpected = nil - }, - ), - Entry("allow normal and extended to be 0 when normal expected is missing and extended expected is in range (upper)", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = nil - datum.ExtendedExpected = pointer.FromFloat64(100.0) - }, - ), - Entry("allow normal and extended to be 0 when both normal expected and extended expected are in range", - func(datum *combination.Combination) { - datum.Duration = pointer.FromInt(0) - datum.Extended = pointer.FromFloat64(0.0) - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(combination.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(combination.NormalMaximum) + datum.DurationExpected = pointer.FromInt(combination.DurationMaximum) + datum.ExtendedExpected = pointer.FromFloat64(combination.ExtendedMaximum) }, ), Entry("multiple errors", @@ -1172,18 +753,18 @@ var _ = Describe("Combination", func() { datum.Duration = nil datum.DurationExpected = pointer.FromInt(86400001) datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", "dual/square"), "/subType", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", combination.SubType), "/subType", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, combination.DurationMaximum), "/expectedDuration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0, 100), "/expectedExtended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.ExtendedMinimum, combination.ExtendedMaximum), "/expectedExtended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0, 100), "/expectedNormal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, combination.NormalMinimum, combination.NormalMaximum), "/expectedNormal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), ), ) }) diff --git a/data/types/bolus/extended/extended.go b/data/types/bolus/extended/extended.go index 5a0dbf694f..25029aa347 100644 --- a/data/types/bolus/extended/extended.go +++ b/data/types/bolus/extended/extended.go @@ -11,7 +11,7 @@ const ( DurationMaximum = 86400000 DurationMinimum = 0 - ExtendedMaximum = 100.0 + ExtendedMaximum = 250.0 ExtendedMinimum = 0.0 ) @@ -61,17 +61,10 @@ func (e *Extended) Validate(validator structure.Validator) { } else { durationExpectedValidator.InRange(DurationMinimum, DurationMaximum) } - if e.ExtendedExpected != nil { - durationExpectedValidator.Exists() - } else { - durationExpectedValidator.NotExists() - } + validator.Float64("extended", e.Extended).Exists().InRange(ExtendedMinimum, ExtendedMaximum) extendedExpectedValidator := validator.Float64("expectedExtended", e.ExtendedExpected) if e.Extended != nil && *e.Extended >= ExtendedMinimum && *e.Extended <= ExtendedMaximum { - if *e.Extended == ExtendedMinimum { - extendedExpectedValidator.Exists() - } extendedExpectedValidator.InRange(*e.Extended, ExtendedMaximum) } else { extendedExpectedValidator.InRange(ExtendedMinimum, ExtendedMaximum) diff --git a/data/types/bolus/extended/extended_test.go b/data/types/bolus/extended/extended_test.go index 2afeb6ace9..49429781a1 100644 --- a/data/types/bolus/extended/extended_test.go +++ b/data/types/bolus/extended/extended_test.go @@ -37,7 +37,7 @@ var _ = Describe("Extended", func() { }) It("ExtendedMaximum is expected", func() { - Expect(extended.ExtendedMaximum).To(Equal(100.0)) + Expect(extended.ExtendedMaximum).To(Equal(250.0)) }) It("ExtendedMinimum is expected", func() { @@ -109,7 +109,7 @@ var _ = Describe("Extended", func() { datum.DurationExpected = pointer.FromInt(-1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration missing; duration expected in range (lower)", func(datum *extended.Extended) { @@ -121,7 +121,7 @@ var _ = Describe("Extended", func() { Entry("duration missing; duration expected in range (upper)", func(datum *extended.Extended) { datum.Duration = nil - datum.DurationExpected = pointer.FromInt(86400000) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), ), @@ -131,7 +131,7 @@ var _ = Describe("Extended", func() { datum.DurationExpected = pointer.FromInt(86400001) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration out of range (lower); duration expected missing", func(datum *extended.Extended) { @@ -139,37 +139,37 @@ var _ = Describe("Extended", func() { datum.DurationExpected = nil datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (lower); duration expected out of range (lower)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration out of range (lower); duration expected in range (lower)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (lower); duration expected in range (upper)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(-1) - datum.DurationExpected = pointer.FromInt(86400000) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (lower); duration expected out of range (upper)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration in range (lower); duration expected missing", func(datum *extended.Extended) { @@ -183,7 +183,7 @@ var _ = Describe("Extended", func() { datum.Duration = pointer.FromInt(0) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration in range (lower); duration expected in range (lower)", func(datum *extended.Extended) { @@ -194,7 +194,7 @@ var _ = Describe("Extended", func() { Entry("duration in range (lower); duration expected in range (upper)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(0) - datum.DurationExpected = pointer.FromInt(86400000) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, ), Entry("duration in range (lower); duration expected out of range (upper)", @@ -202,40 +202,40 @@ var _ = Describe("Extended", func() { datum.Duration = pointer.FromInt(0) datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration in range (upper); duration expected missing", func(datum *extended.Extended) { - datum.Duration = pointer.FromInt(86400000) + datum.Duration = pointer.FromInt(extended.DurationMaximum) datum.DurationExpected = nil datum.ExtendedExpected = nil }, ), Entry("duration in range (upper); duration expected out of range (lower)", func(datum *extended.Extended) { - datum.Duration = pointer.FromInt(86400000) + datum.Duration = pointer.FromInt(extended.DurationMaximum) datum.DurationExpected = pointer.FromInt(604799999) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604799999, 86400000, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604799999, extended.DurationMaximum, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration in range (upper); duration expected in range (lower)", func(datum *extended.Extended) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Duration = pointer.FromInt(extended.DurationMaximum) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, ), Entry("duration in range (upper); duration expected in range (upper)", func(datum *extended.Extended) { - datum.Duration = pointer.FromInt(86400000) - datum.DurationExpected = pointer.FromInt(86400000) + datum.Duration = pointer.FromInt(extended.DurationMaximum) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, ), Entry("duration in range (upper); duration expected out of range (upper)", func(datum *extended.Extended) { - datum.Duration = pointer.FromInt(86400000) + datum.Duration = pointer.FromInt(extended.DurationMaximum) datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 86400000, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, extended.DurationMaximum, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration out of range (upper); duration expected missing", func(datum *extended.Extended) { @@ -243,37 +243,37 @@ var _ = Describe("Extended", func() { datum.DurationExpected = nil datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (upper); duration expected out of range (lower)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(86400001) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("duration out of range (upper); duration expected in range (lower)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(86400001) datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (upper); duration expected in range (upper)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(86400001) - datum.DurationExpected = pointer.FromInt(86400000) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/duration", NewMeta()), ), Entry("duration out of range (upper); duration expected out of range (upper)", func(datum *extended.Extended) { datum.Duration = pointer.FromInt(86400001) datum.DurationExpected = pointer.FromInt(86400001) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/expectedDuration", NewMeta()), ), Entry("extended missing; extended expected missing", func(datum *extended.Extended) { @@ -289,7 +289,7 @@ var _ = Describe("Extended", func() { datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended missing; extended expected in range (lower)", func(datum *extended.Extended) { @@ -301,17 +301,17 @@ var _ = Describe("Extended", func() { Entry("extended missing; extended expected in range (upper)", func(datum *extended.Extended) { datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), ), Entry("extended missing; extended expected out of range (upper)", func(datum *extended.Extended) { datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended out of range (lower); extended expected missing", func(datum *extended.Extended) { @@ -319,52 +319,44 @@ var _ = Describe("Extended", func() { datum.Extended = pointer.FromFloat64(-0.1) datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (lower); extended expected out of range (lower)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(-0.1) datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended out of range (lower); extended expected in range (lower)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(-0.1) datum.ExtendedExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (lower); extended expected in range (upper)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (lower); extended expected out of range (upper)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(-0.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), - ), - Entry("extended in range (lower); extended expected missing", - func(datum *extended.Extended) { - datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended in range (lower); extended expected out of range (lower)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(0.0) datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended in range (lower); extended expected in range (lower)", func(datum *extended.Extended) { @@ -375,86 +367,86 @@ var _ = Describe("Extended", func() { Entry("extended in range (lower); extended expected in range (upper)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, ), Entry("extended in range (lower); extended expected out of range (upper)", func(datum *extended.Extended) { datum.Extended = pointer.FromFloat64(0.0) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended in range (upper); extended expected missing", func(datum *extended.Extended) { datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(100.0) + datum.Extended = pointer.FromFloat64(extended.ExtendedMaximum) datum.ExtendedExpected = nil }, ), Entry("extended in range (upper); extended expected out of range (lower)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(99.9) + datum.Extended = pointer.FromFloat64(extended.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(249.9) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(99.9, 100.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(249.9, extended.ExtendedMaximum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended in range (upper); extended expected in range (lower)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Extended = pointer.FromFloat64(extended.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, ), Entry("extended in range (upper); extended expected in range (upper)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Extended = pointer.FromFloat64(extended.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, ), Entry("extended in range (upper); extended expected out of range (upper)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.0) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Extended = pointer.FromFloat64(extended.ExtendedMaximum) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 100.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMaximum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended out of range (upper); extended expected missing", func(datum *extended.Extended) { datum.DurationExpected = nil - datum.Extended = pointer.FromFloat64(100.1) + datum.Extended = pointer.FromFloat64(250.1) datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (upper); extended expected out of range (lower)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.1) + datum.Extended = pointer.FromFloat64(250.1) datum.ExtendedExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("extended out of range (upper); extended expected in range (lower)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.1) + datum.Extended = pointer.FromFloat64(250.1) datum.ExtendedExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (upper); extended expected in range (upper)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.1) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), ), Entry("extended out of range (upper); extended expected out of range (upper)", func(datum *extended.Extended) { - datum.Extended = pointer.FromFloat64(100.1) - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.Extended = pointer.FromFloat64(250.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/extended", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedExtended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/extended", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", NewMeta()), ), Entry("duration missing; extended expected missing", @@ -466,21 +458,19 @@ var _ = Describe("Extended", func() { Entry("duration missing; extended expected exists", func(datum *extended.Extended) { datum.DurationExpected = nil - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedDuration", NewMeta()), ), Entry("duration exists; extended expected missing", func(datum *extended.Extended) { - datum.DurationExpected = pointer.FromInt(86400000) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) datum.ExtendedExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueExists(), "/expectedDuration", NewMeta()), ), Entry("duration exists; extended expected exists", func(datum *extended.Extended) { - datum.DurationExpected = pointer.FromInt(86400000) - datum.ExtendedExpected = pointer.FromFloat64(100.0) + datum.DurationExpected = pointer.FromInt(extended.DurationMaximum) + datum.ExtendedExpected = pointer.FromFloat64(extended.ExtendedMaximum) }, ), Entry("multiple errors", @@ -490,14 +480,14 @@ var _ = Describe("Extended", func() { datum.Duration = nil datum.DurationExpected = pointer.FromInt(86400001) datum.Extended = nil - datum.ExtendedExpected = pointer.FromFloat64(100.1) + datum.ExtendedExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", "square"), "/subType", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/duration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, 86400000), "/expectedDuration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(86400001, 0, extended.DurationMaximum), "/expectedDuration", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/extended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0, 100), "/expectedExtended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, extended.ExtendedMinimum, extended.ExtendedMaximum), "/expectedExtended", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), ), ) }) diff --git a/data/types/bolus/normal/normal.go b/data/types/bolus/normal/normal.go index 5b939493ba..c387e54794 100644 --- a/data/types/bolus/normal/normal.go +++ b/data/types/bolus/normal/normal.go @@ -9,7 +9,7 @@ import ( const ( SubType = "normal" // TODO: Rename Type to "bolus/normal"; remove SubType - NormalMaximum = 100.0 + NormalMaximum = 250.0 NormalMinimum = 0.0 ) @@ -51,9 +51,6 @@ func (n *Normal) Validate(validator structure.Validator) { validator.Float64("normal", n.Normal).Exists().InRange(NormalMinimum, NormalMaximum) normalExpectedValidator := validator.Float64("expectedNormal", n.NormalExpected) if n.Normal != nil && *n.Normal >= NormalMinimum && *n.Normal <= NormalMaximum { - if *n.Normal == NormalMinimum { - normalExpectedValidator.Exists() - } normalExpectedValidator.InRange(*n.Normal, NormalMaximum) } else { normalExpectedValidator.InRange(NormalMinimum, NormalMaximum) diff --git a/data/types/bolus/normal/normal_test.go b/data/types/bolus/normal/normal_test.go index a385ea10fa..c0586f8f4c 100644 --- a/data/types/bolus/normal/normal_test.go +++ b/data/types/bolus/normal/normal_test.go @@ -29,7 +29,7 @@ var _ = Describe("Normal", func() { }) It("NormalMaximum is expected", func() { - Expect(normal.NormalMaximum).To(Equal(100.0)) + Expect(normal.NormalMaximum).To(Equal(250.0)) }) It("NormalMinimum is expected", func() { @@ -97,7 +97,7 @@ var _ = Describe("Normal", func() { datum.NormalExpected = pointer.FromFloat64(-0.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal missing; normal expected in range (lower)", func(datum *normal.Normal) { @@ -109,68 +109,61 @@ var _ = Describe("Normal", func() { Entry("normal missing; normal expected in range (upper)", func(datum *normal.Normal) { datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), ), Entry("normal missing; normal expected out of range (upper)", func(datum *normal.Normal) { datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (lower); normal expected missing", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected out of range (lower)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (lower); normal expected in range (lower)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(-0.1) datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected in range (upper)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (lower); normal expected out of range (upper)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(-0.1) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), - ), - Entry("normal in range (lower); normal expected missing", - func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (lower); normal expected out of range (lower)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(0.0) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (lower); normal expected in range (lower)", func(datum *normal.Normal) { @@ -181,96 +174,96 @@ var _ = Describe("Normal", func() { Entry("normal in range (lower); normal expected in range (upper)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, ), Entry("normal in range (lower); normal expected out of range (upper)", func(datum *normal.Normal) { datum.Normal = pointer.FromFloat64(0.0) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (upper); normal expected missing", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(normal.NormalMaximum) datum.NormalExpected = nil }, ), Entry("normal in range (upper); normal expected out of range (lower)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(99.9) + datum.Normal = pointer.FromFloat64(normal.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(249.9) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(99.9, 100.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(249.9, normal.NormalMaximum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal in range (upper); normal expected in range (lower)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(normal.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, ), Entry("normal in range (upper); normal expected in range (upper)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(normal.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, ), Entry("normal in range (upper); normal expected out of range (upper)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.0) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(normal.NormalMaximum) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 100.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMaximum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (upper); normal expected missing", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected out of range (lower)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("normal out of range (upper); normal expected in range (lower)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) datum.NormalExpected = pointer.FromFloat64(0.0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected in range (upper)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.0) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(normal.NormalMaximum) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), ), Entry("normal out of range (upper); normal expected out of range (upper)", func(datum *normal.Normal) { - datum.Normal = pointer.FromFloat64(100.1) - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.Normal = pointer.FromFloat64(250.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/normal", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/normal", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", NewMeta()), ), Entry("multiple errors", func(datum *normal.Normal) { datum.Type = "invalidType" datum.SubType = "invalidSubType" datum.Normal = nil - datum.NormalExpected = pointer.FromFloat64(100.1) + datum.NormalExpected = pointer.FromFloat64(250.1) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidType", "bolus"), "/type", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotEqualTo("invalidSubType", "normal"), "/subType", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/normal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/expectedNormal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, normal.NormalMinimum, normal.NormalMaximum), "/expectedNormal", &bolus.Meta{Type: "invalidType", SubType: "invalidSubType"}), ), ) }) diff --git a/data/types/bolus/test/bolus.go b/data/types/bolus/test/bolus.go index b98566a644..0ae9da6e15 100644 --- a/data/types/bolus/test/bolus.go +++ b/data/types/bolus/test/bolus.go @@ -4,6 +4,7 @@ import ( dataTypesBolus "github.com/tidepool-org/platform/data/types/bolus" dataTypesInsulinTest "github.com/tidepool-org/platform/data/types/insulin/test" dataTypesTest "github.com/tidepool-org/platform/data/types/test" + "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/test" ) @@ -25,6 +26,7 @@ func randomBolus() *dataTypesBolus.Bolus { datum := &dataTypesBolus.Bolus{} datum.SubType = dataTypesTest.NewType() datum.InsulinFormulation = dataTypesInsulinTest.RandomFormulation(3) + datum.DeliveryContext = pointer.FromString(test.RandomStringFromArray(dataTypesBolus.DeliveryContexts())) return datum } @@ -36,6 +38,7 @@ func CloneBolus(datum *dataTypesBolus.Bolus) *dataTypesBolus.Bolus { clone.Base = *dataTypesTest.CloneBase(&datum.Base) clone.SubType = datum.SubType clone.InsulinFormulation = dataTypesInsulinTest.CloneFormulation(datum.InsulinFormulation) + clone.DeliveryContext = pointer.CloneString(datum.DeliveryContext) return clone } @@ -48,5 +51,8 @@ func NewObjectFromBolus(datum *dataTypesBolus.Bolus, objectFormat test.ObjectFor if datum.InsulinFormulation != nil { object["insulinFormulation"] = dataTypesInsulinTest.NewObjectFromFormulation(datum.InsulinFormulation, objectFormat) } + if datum.DeliveryContext != nil { + object["deliveryContext"] = test.NewObjectFromString(*datum.DeliveryContext, objectFormat) + } return object } diff --git a/data/types/calculator/calculator.go b/data/types/calculator/calculator.go index 70a72178f0..c414a457c4 100644 --- a/data/types/calculator/calculator.go +++ b/data/types/calculator/calculator.go @@ -17,7 +17,7 @@ const ( CarbohydrateInputMaximum = 1000.0 CarbohydrateInputMinimum = 0.0 - InsulinCarbohydrateRatioMaximum = 250.0 + InsulinCarbohydrateRatioMaximum = 500.0 InsulinCarbohydrateRatioMinimum = 0.0 InsulinOnBoardMaximum = 250.0 InsulinOnBoardMinimum = 0.0 @@ -118,6 +118,10 @@ func (c *Calculator) Validate(validator structure.Validator) { } } +func (c *Calculator) IdentityFields(version int) ([]string, error) { + return c.Base.IdentityFields(version) +} + func (c *Calculator) Normalize(normalizer data.Normalizer) { if !normalizer.HasMeta() { normalizer = normalizer.WithMeta(c.Meta()) diff --git a/data/types/calculator/calculator_test.go b/data/types/calculator/calculator_test.go index e68ab3ca10..72e387b731 100644 --- a/data/types/calculator/calculator_test.go +++ b/data/types/calculator/calculator_test.go @@ -1,6 +1,8 @@ package calculator_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -121,7 +123,7 @@ var _ = Describe("Calculator", func() { }) It("InsulinCarbohydrateRatioMaximum is expected", func() { - Expect(calculator.InsulinCarbohydrateRatioMaximum).To(Equal(250.0)) + Expect(calculator.InsulinCarbohydrateRatioMaximum).To(Equal(500.0)) }) It("InsulinCarbohydrateRatioMinimum is expected", func() { @@ -154,6 +156,30 @@ var _ = Describe("Calculator", func() { Expect(datum.CarbUnits).To(BeNil()) }) }) + Context("Legacy IdentityFields", func() { + var datum *calculator.Calculator + + BeforeEach(func() { + datum = NewCalculator(pointer.FromString("mmol/L")) + }) + + It("returns error if delivery type is empty", func() { + datum.Type = "" + identityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("type is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns the expected legacy identity fields", func() { + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"wizard", "some-device", "2023-05-13T15:51:58.000Z"})) + }) + }) Context("Calculator", func() { Context("Parse", func() { @@ -285,7 +311,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/units", NewMeta()), ), Entry("units missing; insulin carbohydrate ratio in range (lower)", @@ -298,16 +324,16 @@ var _ = Describe("Calculator", func() { Entry("units missing; insulin carbohydrate ratio in range (upper)", nil, func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/units", NewMeta()), ), Entry("units missing; insulin carbohydrate ratio out of range (upper)", nil, func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/units", NewMeta()), ), Entry("units missing; insulin on board missing", @@ -378,7 +404,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/units", NewMeta()), ), Entry("units missing; recommended valid", @@ -486,7 +512,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"mmol/L", "mmol/l", "mg/dL", "mg/dl"}), "/units", NewMeta()), ), Entry("units invalid; insulin carbohydrate ratio in range (lower)", @@ -499,16 +525,16 @@ var _ = Describe("Calculator", func() { Entry("units invalid; insulin carbohydrate ratio in range (upper)", pointer.FromString("invalid"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"mmol/L", "mmol/l", "mg/dL", "mg/dl"}), "/units", NewMeta()), ), Entry("units invalid; insulin carbohydrate ratio out of range (upper)", pointer.FromString("invalid"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"mmol/L", "mmol/l", "mg/dL", "mg/dl"}), "/units", NewMeta()), ), Entry("units invalid; insulin on board missing", @@ -579,7 +605,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"mmol/L", "mmol/l", "mg/dL", "mg/dl"}), "/units", NewMeta()), ), Entry("units invalid; recommended valid", @@ -669,7 +695,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mmol/L; insulin carbohydrate ratio in range (lower)", pointer.FromString("mmol/L"), @@ -680,15 +706,15 @@ var _ = Describe("Calculator", func() { Entry("units mmol/L; insulin carbohydrate ratio in range (upper)", pointer.FromString("mmol/L"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, ), Entry("units mmol/L; insulin carbohydrate ratio out of range (upper)", pointer.FromString("mmol/L"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mmol/L; insulin on board missing", pointer.FromString("mmol/L"), @@ -749,7 +775,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), ), Entry("units mmol/L; recommended valid", pointer.FromString("mmol/L"), @@ -837,7 +863,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mmol/l; insulin carbohydrate ratio in range (lower)", pointer.FromString("mmol/l"), @@ -848,15 +874,15 @@ var _ = Describe("Calculator", func() { Entry("units mmol/l; insulin carbohydrate ratio in range (upper)", pointer.FromString("mmol/l"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, ), Entry("units mmol/l; insulin carbohydrate ratio out of range (upper)", pointer.FromString("mmol/l"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mmol/l; insulin on board missing", pointer.FromString("mmol/l"), @@ -917,7 +943,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), ), Entry("units mmol/l; recommended valid", pointer.FromString("mmol/l"), @@ -1009,7 +1035,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mg/dL; insulin carbohydrate ratio in range (lower)", pointer.FromString("mg/dL"), @@ -1020,15 +1046,15 @@ var _ = Describe("Calculator", func() { Entry("units mg/dL; insulin carbohydrate ratio in range (upper)", pointer.FromString("mg/dL"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, ), Entry("units mg/dL; insulin carbohydrate ratio out of range (upper)", pointer.FromString("mg/dL"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mg/dL; insulin on board missing", pointer.FromString("mg/dL"), @@ -1089,7 +1115,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), ), Entry("units mg/dL; recommended valid", pointer.FromString("mg/dL"), @@ -1181,7 +1207,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.InsulinCarbohydrateRatio = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mg/dl; insulin carbohydrate ratio in range (lower)", pointer.FromString("mg/dl"), @@ -1192,15 +1218,15 @@ var _ = Describe("Calculator", func() { Entry("units mg/dl; insulin carbohydrate ratio in range (upper)", pointer.FromString("mg/dl"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.0) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum) }, ), Entry("units mg/dl; insulin carbohydrate ratio out of range (upper)", pointer.FromString("mg/dl"), func(datum *calculator.Calculator, units *string) { - datum.InsulinCarbohydrateRatio = pointer.FromFloat64(250.1) + datum.InsulinCarbohydrateRatio = pointer.FromFloat64(calculator.InsulinCarbohydrateRatioMaximum + 0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(250.1, 0.0, 250.0), "/insulinCarbRatio", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(calculator.InsulinCarbohydrateRatioMaximum+0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/insulinCarbRatio", NewMeta()), ), Entry("units mg/dl; insulin on board missing", pointer.FromString("mg/dl"), @@ -1261,7 +1287,7 @@ var _ = Describe("Calculator", func() { func(datum *calculator.Calculator, units *string) { datum.Recommended.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/recommended/carb", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-0.1, 0.0, calculator.InsulinCarbohydrateRatioMaximum), "/recommended/carb", NewMeta()), ), Entry("units mg/dl; recommended valid", pointer.FromString("mg/dl"), diff --git a/data/types/calculator/recommended.go b/data/types/calculator/recommended.go index 69168e7801..9fbd9821bb 100644 --- a/data/types/calculator/recommended.go +++ b/data/types/calculator/recommended.go @@ -6,12 +6,12 @@ import ( ) const ( - CarbohydrateMaximum = 100.0 + CarbohydrateMaximum = 500.0 CarbohydrateMinimum = 0.0 - CorrectionMaximum = 100.0 - CorrectionMinimum = -100.0 - NetMaximum = 100.0 - NetMinimum = -100.0 + CorrectionMaximum = 250.0 + CorrectionMinimum = -250.0 + NetMaximum = 250.0 + NetMinimum = -250.0 ) type Recommended struct { diff --git a/data/types/calculator/recommended_test.go b/data/types/calculator/recommended_test.go index 5cf7a51ecb..387cb64507 100644 --- a/data/types/calculator/recommended_test.go +++ b/data/types/calculator/recommended_test.go @@ -36,7 +36,7 @@ func CloneRecommended(datum *calculator.Recommended) *calculator.Recommended { var _ = Describe("Recommended", func() { It("CarbohydrateMaximum is expected", func() { - Expect(calculator.CarbohydrateMaximum).To(Equal(100.0)) + Expect(calculator.CarbohydrateMaximum).To(Equal(500.0)) }) It("CarbohydrateMinimum is expected", func() { @@ -44,19 +44,19 @@ var _ = Describe("Recommended", func() { }) It("CorrectionMaximum is expected", func() { - Expect(calculator.CorrectionMaximum).To(Equal(100.0)) + Expect(calculator.CorrectionMaximum).To(Equal(250.0)) }) It("CorrectionMinimum is expected", func() { - Expect(calculator.CorrectionMinimum).To(Equal(-100.0)) + Expect(calculator.CorrectionMinimum).To(Equal(-250.0)) }) It("NetMaximum is expected", func() { - Expect(calculator.NetMaximum).To(Equal(100.0)) + Expect(calculator.NetMaximum).To(Equal(250.0)) }) It("NetMinimum is expected", func() { - Expect(calculator.NetMinimum).To(Equal(-100.0)) + Expect(calculator.NetMinimum).To(Equal(-250.0)) }) Context("ParseRecommended", func() { @@ -89,7 +89,7 @@ var _ = Describe("Recommended", func() { ), Entry("carbohydrate out of range (lower)", func(datum *calculator.Recommended) { datum.Carbohydrate = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-0.1, 0, 100), "/carb"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CarbohydrateMinimum-0.1, calculator.CarbohydrateMinimum, calculator.CarbohydrateMaximum), "/carb"), ), Entry("carbohydrate in range (lower)", func(datum *calculator.Recommended) { datum.Carbohydrate = pointer.FromFloat64(0.0) }, @@ -98,15 +98,17 @@ var _ = Describe("Recommended", func() { func(datum *calculator.Recommended) { datum.Carbohydrate = pointer.FromFloat64(100.0) }, ), Entry("carbohydrate out of range (upper)", - func(datum *calculator.Recommended) { datum.Carbohydrate = pointer.FromFloat64(100.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(100.1, 0, 100), "/carb"), + func(datum *calculator.Recommended) { + datum.Carbohydrate = pointer.FromFloat64(calculator.CarbohydrateMaximum + 0.1) + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CarbohydrateMaximum+0.1, calculator.CarbohydrateMinimum, calculator.CarbohydrateMaximum), "/carb"), ), Entry("correction missing", func(datum *calculator.Recommended) { datum.Correction = nil }, ), Entry("correction out of range (lower)", - func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(-100.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-100.1, -100, 100), "/correction"), + func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(-250.1) }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CorrectionMinimum-0.1, calculator.CorrectionMinimum, calculator.CorrectionMaximum), "/correction"), ), Entry("correction in range (lower)", func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(-100.0) }, @@ -115,15 +117,15 @@ var _ = Describe("Recommended", func() { func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(100.0) }, ), Entry("correction out of range (upper)", - func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(100.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(100.1, -100, 100), "/correction"), + func(datum *calculator.Recommended) { datum.Correction = pointer.FromFloat64(250.1) }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CorrectionMaximum+0.1, calculator.CorrectionMinimum, calculator.CorrectionMaximum), "/correction"), ), Entry("net missing", func(datum *calculator.Recommended) { datum.Net = nil }, ), Entry("net out of range (lower)", - func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(-100.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-100.1, -100, 100), "/net"), + func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(-250.1) }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.NetMinimum-0.1, calculator.NetMinimum, calculator.NetMaximum), "/net"), ), Entry("net in range (lower)", func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(-100.0) }, @@ -132,18 +134,18 @@ var _ = Describe("Recommended", func() { func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(100.0) }, ), Entry("net out of range (upper)", - func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(100.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(100.1, -100, 100), "/net"), + func(datum *calculator.Recommended) { datum.Net = pointer.FromFloat64(250.1) }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.NetMaximum+0.1, calculator.NetMinimum, calculator.NetMaximum), "/net"), ), Entry("multiple errors", func(datum *calculator.Recommended) { datum.Carbohydrate = pointer.FromFloat64(-0.1) - datum.Correction = pointer.FromFloat64(-100.1) - datum.Net = pointer.FromFloat64(-100.1) + datum.Correction = pointer.FromFloat64(-250.1) + datum.Net = pointer.FromFloat64(-250.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-0.1, 0, 100), "/carb"), - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-100.1, -100, 100), "/correction"), - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-100.1, -100, 100), "/net"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CarbohydrateMinimum-0.1, calculator.CarbohydrateMinimum, calculator.InsulinCarbohydrateRatioMaximum), "/carb"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.CorrectionMinimum-0.1, calculator.CorrectionMinimum, calculator.CorrectionMaximum), "/correction"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(calculator.NetMinimum-0.1, calculator.NetMinimum, calculator.NetMaximum), "/net"), ), ) }) diff --git a/data/types/common/common_suite_test.go b/data/types/common/common_suite_test.go new file mode 100644 index 0000000000..5f35361ef7 --- /dev/null +++ b/data/types/common/common_suite_test.go @@ -0,0 +1,11 @@ +package common_test + +import ( + "testing" + + "github.com/tidepool-org/platform/test" +) + +func TestSuite(t *testing.T) { + test.Test(t) +} diff --git a/data/types/common/day.go b/data/types/common/day.go new file mode 100644 index 0000000000..68e7d150c2 --- /dev/null +++ b/data/types/common/day.go @@ -0,0 +1,93 @@ +package common + +import ( + "slices" + "strings" + + "github.com/tidepool-org/platform/errors" + "github.com/tidepool-org/platform/structure" + "github.com/tidepool-org/platform/structure/validator" +) + +const ( + DaySunday = "sunday" + DayMonday = "monday" + DayTuesday = "tuesday" + DayWednesday = "wednesday" + DayThursday = "thursday" + DayFriday = "friday" + DaySaturday = "saturday" +) + +func DaysOfWeek() []string { + return []string{ + DaySunday, + DayMonday, + DayTuesday, + DayWednesday, + DayThursday, + DayFriday, + DaySaturday, + } +} + +type DaysOfWeekByDayIndex []string + +func (d DaysOfWeekByDayIndex) Len() int { + return len(d) +} +func (d DaysOfWeekByDayIndex) Swap(i int, j int) { + d[i], d[j] = d[j], d[i] +} + +func (d DaysOfWeekByDayIndex) Less(i int, j int) bool { + iDay := d[i] + jDay := d[j] + iDayIndex, iErr := DayIndex(iDay) + jDayIndex, jErr := DayIndex(jDay) + if iErr != nil { + if jErr != nil { + return iDay < jDay + } else { + return false + } + } else if jErr != nil { + return true + } else { + return iDayIndex < jDayIndex + } +} + +func DayIndex(day string) (int, error) { + switch day { + case DaySunday: + return 1, nil + case DayMonday: + return 2, nil + case DayTuesday: + return 3, nil + case DayWednesday: + return 4, nil + case DayThursday: + return 5, nil + case DayFriday: + return 6, nil + case DaySaturday: + return 7, nil + default: + return 0, errors.New("invalid day of the week") + } +} + +func ValidateDayOfWeek(value string) error { + if value == "" { + return validator.ErrorValueEmpty() + } else if !slices.Contains(DaysOfWeek(), strings.ToLower(value)) { + return validator.ErrorValueStringNotOneOf(value, DaysOfWeek()) + } + return nil +} + +func DayOfWeekValidator(value string, errorReporter structure.ErrorReporter) { + errorReporter.ReportError(ValidateDayOfWeek(value)) +} diff --git a/data/types/common/day_test.go b/data/types/common/day_test.go new file mode 100644 index 0000000000..d5b9936761 --- /dev/null +++ b/data/types/common/day_test.go @@ -0,0 +1,101 @@ +package common_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/tidepool-org/platform/data/types/common" + "github.com/tidepool-org/platform/errors" + "github.com/tidepool-org/platform/structure/validator" +) + +var _ = Describe("Day", func() { + + It("DaySunday is expected", func() { + Expect(common.DaySunday).To(Equal("sunday")) + }) + + It("DayMonday is expected", func() { + Expect(common.DayMonday).To(Equal("monday")) + }) + + It("DayTuesday is expected", func() { + Expect(common.DayTuesday).To(Equal("tuesday")) + }) + + It("DayWednesday is expected", func() { + Expect(common.DayWednesday).To(Equal("wednesday")) + }) + + It("DayThursday is expected", func() { + Expect(common.DayThursday).To(Equal("thursday")) + }) + + It("DayFriday is expected", func() { + Expect(common.DayFriday).To(Equal("friday")) + }) + + It("DaySaturday is expected", func() { + Expect(common.DaySaturday).To(Equal("saturday")) + }) + + It("DaysOfWeek returns expected", func() { + Expect(common.DaysOfWeek()).To(Equal([]string{"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"})) + Expect(common.DaysOfWeek()).To(Equal([]string{ + common.DaySunday, + common.DayMonday, + common.DayTuesday, + common.DayWednesday, + common.DayThursday, + common.DayFriday, + common.DaySaturday, + })) + }) + + Context("DayIndex", func() { + DescribeTable("return the expected index when the day", + func(day string, expectedIndex int, expectedErr error) { + actualIndex, actualError := common.DayIndex(day) + Expect(actualIndex).To(Equal(expectedIndex)) + if expectedErr == nil { + Expect(actualError).To(BeNil()) + } else { + Expect(actualError.Error()).To(Equal(expectedErr.Error())) + } + }, + Entry("is an empty string", "", 0, errors.New("invalid day of the week")), + Entry("is sunday", "sunday", 1, nil), + Entry("is constant sunday", common.DaySunday, 1, nil), + Entry("is monday", "monday", 2, nil), + Entry("is constant monday", common.DayMonday, 2, nil), + Entry("is tuesday", "tuesday", 3, nil), + Entry("is constant tuesday", common.DayTuesday, 3, nil), + Entry("is wednesday", "wednesday", 4, nil), + Entry("is constant wednesday", common.DayWednesday, 4, nil), + Entry("is thursday", "thursday", 5, nil), + Entry("is constant thursday", common.DayThursday, 5, nil), + Entry("is friday", "friday", 6, nil), + Entry("is constant friday", common.DayFriday, 6, nil), + Entry("is saturday", "saturday", 7, nil), + Entry("is constant saturday", common.DaySaturday, 7, nil), + Entry("is an invalid string", "invalid", 0, errors.New("invalid day of the week")), + ) + }) + + Context("ValidateDayOfWeek", func() { + DescribeTable("return error when invalid", + func(day string, expectedErr error) { + actualError := common.ValidateDayOfWeek(day) + if expectedErr == nil { + Expect(actualError).To(BeNil()) + } else { + Expect(actualError.Error()).To(Equal(expectedErr.Error())) + } + }, + Entry("ok when same case", "tuesday", nil), + Entry("ok when mixed case", "FriDAY", nil), + Entry("ok when uppercase", "SUNDAY", nil), + Entry("invalid when not a day of the week", "monday2", validator.ErrorValueStringNotOneOf("monday2", common.DaysOfWeek())), + ) + }) +}) diff --git a/data/types/device/device.go b/data/types/device/device.go index 3da51cb06e..7dccb67e04 100644 --- a/data/types/device/device.go +++ b/data/types/device/device.go @@ -2,7 +2,6 @@ package device import ( "github.com/tidepool-org/platform/data/types" - "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/structure" ) @@ -45,15 +44,41 @@ func (d *Device) Validate(validator structure.Validator) { validator.String("subType", &d.SubType).Exists().NotEmpty() } -func (d *Device) IdentityFields() ([]string, error) { - identityFields, err := d.Base.IdentityFields() +func (d *Device) IdentityFields(version int) ([]string, error) { + identityFields := []string{} + var err error + if version == types.LegacyIdentityFieldsVersion { + + identityFields, err = types.AppendIdentityFieldVal(identityFields, &d.Type, "type") + if err != nil { + return nil, err + } + + identityFields, err = types.AppendIdentityFieldVal(identityFields, &d.SubType, "sub type") + if err != nil { + return nil, err + } + + identityFields, err = types.AppendLegacyTimeVal(identityFields, d.Time) + if err != nil { + return nil, err + } + + identityFields, err = types.AppendIdentityFieldVal(identityFields, d.DeviceID, "device id") + if err != nil { + return nil, err + } + + return identityFields, nil + } + + identityFields, err = d.Base.IdentityFields(version) if err != nil { return nil, err } - - if d.SubType == "" { - return nil, errors.New("sub type is empty") + identityFields, err = types.AppendIdentityFieldVal(identityFields, &d.SubType, "sub type") + if err != nil { + return nil, err } - - return append(identityFields, d.SubType), nil + return identityFields, nil } diff --git a/data/types/device/device_test.go b/data/types/device/device_test.go index 8a6ad0e2af..27a4f44da2 100644 --- a/data/types/device/device_test.go +++ b/data/types/device/device_test.go @@ -7,6 +7,8 @@ import ( . "github.com/onsi/gomega" "github.com/tidepool-org/platform/data" + "github.com/tidepool-org/platform/data/types" + dataTypes "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/device" dataTypesDeviceTest "github.com/tidepool-org/platform/data/types/device/test" dataTypesTest "github.com/tidepool-org/platform/data/types/test" @@ -93,6 +95,7 @@ var _ = Describe("Device", func() { }) Context("IdentityFields", func() { + const currentVersion = types.IdentityFieldsVersion var datumDevice *device.Device var datum data.Datum @@ -103,30 +106,56 @@ var _ = Describe("Device", func() { It("returns error if user id is missing", func() { datumDevice.UserID = nil - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is missing")) Expect(identityFields).To(BeEmpty()) }) It("returns error if user id is empty", func() { datumDevice.UserID = pointer.FromString("") - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("user id is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns error if sub type is empty", func() { datumDevice.SubType = "" - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).To(MatchError("sub type is empty")) Expect(identityFields).To(BeEmpty()) }) It("returns the expected identity fields", func() { - identityFields, err := datum.IdentityFields() + identityFields, err := datum.IdentityFields(currentVersion) Expect(err).ToNot(HaveOccurred()) Expect(identityFields).To(Equal([]string{*datumDevice.UserID, *datumDevice.DeviceID, (*datumDevice.Time).Format(ExpectedTimeFormat), datumDevice.Type, datumDevice.SubType})) }) }) + + Context("Legacy IdentityFields", func() { + var datum *device.Device + + BeforeEach(func() { + datum = dataTypesDeviceTest.RandomDevice() + }) + + It("returns error if sub type is empty", func() { + datum.SubType = "" + identityFields, err := datum.IdentityFields(dataTypes.LegacyIdentityFieldsVersion) + Expect(err).To(MatchError("sub type is empty")) + Expect(identityFields).To(BeEmpty()) + }) + + It("returns the expected legacy identity fields", func() { + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(dataTypes.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + datum.SubType = "some-sub-type" + legacyIdentityFields, err := datum.IdentityFields(dataTypes.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"deviceEvent", "some-sub-type", "2023-05-13T15:51:58.000Z", "some-device"})) + }) + }) }) }) diff --git a/data/types/device/override/settings/pump/pump.go b/data/types/device/override/settings/pump/pump.go index 735cde3059..11c3949045 100644 --- a/data/types/device/override/settings/pump/pump.go +++ b/data/types/device/override/settings/pump/pump.go @@ -15,7 +15,7 @@ const ( BasalRateScaleFactorMinimum = 0.1 CarbohydrateRatioScaleFactorMaximum = 10.0 CarbohydrateRatioScaleFactorMinimum = 0.1 - DurationMaximum = 604800 // 7 days in seconds + DurationMaximum = 604800000 // 7 days in milliseconds DurationMinimum = 0 InsulinSensitivityScaleFactorMaximum = 10.0 InsulinSensitivityScaleFactorMinimum = 0.1 @@ -131,10 +131,6 @@ func (p *Pump) Validate(validator structure.Validator) { } else if p.BloodGlucoseTarget != nil { unitsValidator.ReportError(structureValidator.ErrorValueNotExists()) } - - if p.BloodGlucoseTarget == nil && p.BasalRateScaleFactor == nil && p.CarbohydrateRatioScaleFactor == nil && p.InsulinSensitivityScaleFactor == nil { - validator.ReportError(structureValidator.ErrorValuesNotExistForAny("bgTarget", "basalRateScaleFactor", "carbRatioScaleFactor", "insulinSensitivityScaleFactor")) - } } func (p *Pump) Normalize(normalizer data.Normalizer) { diff --git a/data/types/device/override/settings/pump/pump_test.go b/data/types/device/override/settings/pump/pump_test.go index c374cda083..f85acd79d5 100644 --- a/data/types/device/override/settings/pump/pump_test.go +++ b/data/types/device/override/settings/pump/pump_test.go @@ -49,7 +49,7 @@ var _ = Describe("Pump", func() { }) It("DurationMaximum is expected", func() { - Expect(dataTypesDeviceOverrideSettingsPump.DurationMaximum).To(Equal(604800)) + Expect(dataTypesDeviceOverrideSettingsPump.DurationMaximum).To(Equal(604800000)) }) It("DurationMinimum is expected", func() { @@ -370,7 +370,7 @@ var _ = Describe("Pump", func() { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), ), Entry("duration; in range (lower)", pointer.FromString("mmol/L"), @@ -389,15 +389,15 @@ var _ = Describe("Pump", func() { Entry("duration; out of range (upper)", pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { - datum.Duration = pointer.FromInt(604801) + datum.Duration = pointer.FromInt(604800000 + 1) datum.DurationExpected = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604801, 0, 604800), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800000+1, 0, 604800000), "/duration", NewMeta()), ), Entry("duration expected missing", pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { - datum.Duration = pointer.FromInt(test.RandomIntFromRange(0, 604800)) + datum.Duration = pointer.FromInt(test.RandomIntFromRange(0, 604800000)) datum.DurationExpected = nil }, ), @@ -407,7 +407,7 @@ var _ = Describe("Pump", func() { datum.Duration = nil datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), ), Entry("duration expected; duration missing; in range (lower)", pointer.FromString("mmol/L"), @@ -420,16 +420,16 @@ var _ = Describe("Pump", func() { pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { datum.Duration = nil - datum.DurationExpected = pointer.FromInt(604800) + datum.DurationExpected = pointer.FromInt(604800000) }, ), Entry("duration expected; duration missing; out of range (upper)", pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { datum.Duration = nil - datum.DurationExpected = pointer.FromInt(604801) + datum.DurationExpected = pointer.FromInt(604800000 + 1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604801, 0, 604800), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800000+1, 0, 604800000), "/expectedDuration", NewMeta()), ), Entry("duration expected; duration out of range; out of range (lower)", pointer.FromString("mmol/L"), @@ -437,8 +437,8 @@ var _ = Describe("Pump", func() { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(-1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration", NewMeta()), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", NewMeta()), ), Entry("duration expected; duration out of range; in range (lower)", pointer.FromString("mmol/L"), @@ -446,7 +446,7 @@ var _ = Describe("Pump", func() { datum.Duration = pointer.FromInt(-1) datum.DurationExpected = pointer.FromInt(0) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", NewMeta()), ), Entry("duration expected; out of range (lower)", pointer.FromString("mmol/L"), @@ -454,7 +454,7 @@ var _ = Describe("Pump", func() { datum.Duration = pointer.FromInt(3600) datum.DurationExpected = pointer.FromInt(3599) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(3599, 3600, 604800), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(3599, 3600, 604800000), "/expectedDuration", NewMeta()), ), Entry("duration expected; in range (lower)", pointer.FromString("mmol/L"), @@ -474,9 +474,9 @@ var _ = Describe("Pump", func() { pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { datum.Duration = pointer.FromInt(3600) - datum.DurationExpected = pointer.FromInt(604801) + datum.DurationExpected = pointer.FromInt(604800000 + 1) }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604801, 3600, 604800), "/expectedDuration", NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(604800000+1, 3600, 604800000), "/expectedDuration", NewMeta()), ), Entry("units mmol/L; blood glucose target missing", pointer.FromString("mmol/L"), @@ -679,17 +679,6 @@ var _ = Describe("Pump", func() { datum.BloodGlucoseTarget = dataBloodGlucoseTest.RandomTarget(unitsBloodGlucose) }, ), - Entry("one of required missing", - pointer.FromString("mmol/L"), - func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { - datum.BloodGlucoseTarget = nil - datum.BasalRateScaleFactor = nil - datum.CarbohydrateRatioScaleFactor = nil - datum.InsulinSensitivityScaleFactor = nil - datum.Units = nil - }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValuesNotExistForAny("bgTarget", "basalRateScaleFactor", "carbRatioScaleFactor", "insulinSensitivityScaleFactor"), "", NewMeta()), - ), Entry("multiple errors", pointer.FromString("mmol/L"), func(datum *dataTypesDeviceOverrideSettingsPump.Pump, unitsBloodGlucose *string) { @@ -708,8 +697,8 @@ var _ = Describe("Pump", func() { errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/overrideType", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueExists(), "/overridePreset", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"automatic", "manual"}), "/method", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/expectedDuration", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/expectedDuration", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/basalRateScaleFactor", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/carbRatioScaleFactor", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/insulinSensitivityScaleFactor", &dataTypesDevice.Meta{Type: "deviceEvent", SubType: "invalidSubType"}), diff --git a/data/types/food/food.go b/data/types/food/food.go index 41734f963f..1921b0f887 100644 --- a/data/types/food/food.go +++ b/data/types/food/food.go @@ -104,3 +104,7 @@ func (f *Food) Normalize(normalizer data.Normalizer) { f.Base.Normalize(normalizer) } + +func (f *Food) IdentityFields(version int) ([]string, error) { + return f.Base.IdentityFields(version) +} diff --git a/data/types/food/food_test.go b/data/types/food/food_test.go index 22e4f43f31..072c8723b4 100644 --- a/data/types/food/food_test.go +++ b/data/types/food/food_test.go @@ -1,6 +1,8 @@ package food_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -432,5 +434,18 @@ var _ = Describe("Food", func() { ), ) }) + + Context("Legacy IdentityFields", func() { + It("returns the expected legacy identity fields", func() { + datum := dataTypesFoodTest.RandomFood(3) + datum.DeviceID = pointer.FromString("some-device") + t, err := time.Parse(dataTypes.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(dataTypes.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"food", "some-device", "2023-05-13T15:51:58.000Z"})) + }) + }) }) }) diff --git a/data/types/insulin/insulin.go b/data/types/insulin/insulin.go index 00198cfac3..e2029588f9 100644 --- a/data/types/insulin/insulin.go +++ b/data/types/insulin/insulin.go @@ -72,3 +72,7 @@ func (i *Insulin) Normalize(normalizer data.Normalizer) { i.Formulation.Normalize(normalizer.WithReference("formulation")) } } + +func (i *Insulin) IdentityFields(version int) ([]string, error) { + return i.Base.IdentityFields(version) +} diff --git a/data/types/insulin/insulin_test.go b/data/types/insulin/insulin_test.go index 129d35061c..21f3153ddb 100644 --- a/data/types/insulin/insulin_test.go +++ b/data/types/insulin/insulin_test.go @@ -1,6 +1,8 @@ package insulin_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -167,5 +169,18 @@ var _ = Describe("Insulin", func() { ), ) }) + + Context("Legacy IdentityFields", func() { + It("returns the expected legacy identity fields", func() { + datum := NewInsulin() + datum.DeviceID = pointer.FromString("some-pump-device") + t, err := time.Parse(types.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(types.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"insulin", "some-pump-device", "2023-05-13T15:51:58.000Z"})) + }) + }) }) }) diff --git a/data/types/settings/cgm/cgm.go b/data/types/settings/cgm/cgm.go index a56223547c..248dabc40d 100644 --- a/data/types/settings/cgm/cgm.go +++ b/data/types/settings/cgm/cgm.go @@ -6,6 +6,7 @@ import ( "github.com/tidepool-org/platform/data" dataBloodGlucose "github.com/tidepool-org/platform/data/blood/glucose" + "github.com/tidepool-org/platform/data/types" dataTypes "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/structure" @@ -156,6 +157,30 @@ func (c *CGM) Normalize(normalizer data.Normalizer) { } } +func (c *CGM) IdentityFields(version int) ([]string, error) { + identityFields := []string{} + var err error + if version == types.LegacyIdentityFieldsVersion { + + identityFields, err = dataTypes.AppendIdentityFieldVal(identityFields, &c.Type, "type") + if err != nil { + return nil, err + } + + identityFields, err = dataTypes.AppendLegacyTimeVal(identityFields, c.Time) + if err != nil { + return nil, err + } + + identityFields, err = dataTypes.AppendIdentityFieldVal(identityFields, c.DeviceID, "device id") + if err != nil { + return nil, err + } + return identityFields, nil + } + return c.Base.IdentityFields(version) +} + func IsValidTransmitterID(value string) bool { return ValidateTransmitterID(value) == nil } diff --git a/data/types/settings/cgm/cgm_test.go b/data/types/settings/cgm/cgm_test.go index 166e106e79..40631be26d 100644 --- a/data/types/settings/cgm/cgm_test.go +++ b/data/types/settings/cgm/cgm_test.go @@ -2,6 +2,7 @@ package cgm_test import ( "sort" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -595,6 +596,19 @@ var _ = Describe("CGM", func() { ), ) }) + + Context("Legacy IdentityFields", func() { + It("returns the expected legacy identity fields", func() { + datum := dataTypesSettingsCgmTest.RandomCGM(pointer.FromString("mmol/l")) + datum.DeviceID = pointer.FromString("some-cgm-device") + t, err := time.Parse(dataTypes.TimeFormat, "2023-05-13T15:51:58Z") + Expect(err).ToNot(HaveOccurred()) + datum.Time = pointer.FromTime(t) + legacyIdentityFields, err := datum.IdentityFields(dataTypes.LegacyIdentityFieldsVersion) + Expect(err).ToNot(HaveOccurred()) + Expect(legacyIdentityFields).To(Equal([]string{"cgmSettings", "2023-05-13T15:51:58.000Z", "some-cgm-device"})) + }) + }) }) Context("IsValidTransmitterID, TransmitterIDValidator, ValidateTransmitterID", func() { diff --git a/data/types/settings/cgm/scheduled_alert.go b/data/types/settings/cgm/scheduled_alert.go index 7a374ed9f0..566e86d264 100644 --- a/data/types/settings/cgm/scheduled_alert.go +++ b/data/types/settings/cgm/scheduled_alert.go @@ -3,6 +3,7 @@ package cgm import ( "strconv" + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" "github.com/tidepool-org/platform/structure" structureValidator "github.com/tidepool-org/platform/structure/validator" ) @@ -12,14 +13,6 @@ const ( ScheduledAlertNameLengthMaximum = 100 - ScheduledAlertDaysSunday = "sunday" - ScheduledAlertDaysMonday = "monday" - ScheduledAlertDaysTuesday = "tuesday" - ScheduledAlertDaysWednesday = "wednesday" - ScheduledAlertDaysThursday = "thursday" - ScheduledAlertDaysFriday = "friday" - ScheduledAlertDaysSaturday = "saturday" - ScheduledAlertStartMaximum = 86400000 ScheduledAlertStartMinimum = 0 @@ -27,18 +20,6 @@ const ( ScheduledAlertEndMinimum = 0 ) -func ScheduledAlertDays() []string { - return []string{ - ScheduledAlertDaysSunday, - ScheduledAlertDaysMonday, - ScheduledAlertDaysTuesday, - ScheduledAlertDaysWednesday, - ScheduledAlertDaysThursday, - ScheduledAlertDaysFriday, - ScheduledAlertDaysSaturday, - } -} - type ScheduledAlerts []*ScheduledAlert func ParseScheduledAlerts(parser structure.ArrayParser) *ScheduledAlerts { @@ -107,7 +88,7 @@ func (s *ScheduledAlert) Parse(parser structure.ObjectParser) { func (s *ScheduledAlert) Validate(validator structure.Validator) { validator.String("name", s.Name).NotEmpty().LengthLessThanOrEqualTo(ScheduledAlertNameLengthMaximum) - validator.StringArray("days", s.Days).Exists().EachOneOf(ScheduledAlertDays()...).EachUnique() + validator.StringArray("days", s.Days).Exists().EachUsing(dataTypesCommon.DayOfWeekValidator).EachUnique() validator.Int("start", s.Start).Exists().InRange(ScheduledAlertStartMinimum, ScheduledAlertStartMaximum) validator.Int("end", s.End).Exists().InRange(ScheduledAlertEndMinimum, ScheduledAlertEndMaximum) if alertsValidator := validator.WithReference("alerts"); s.Alerts != nil { diff --git a/data/types/settings/cgm/scheduled_alert_test.go b/data/types/settings/cgm/scheduled_alert_test.go index 87b4bc8ebf..ecf37f5286 100644 --- a/data/types/settings/cgm/scheduled_alert_test.go +++ b/data/types/settings/cgm/scheduled_alert_test.go @@ -5,6 +5,8 @@ import ( . "github.com/onsi/gomega" dataTypesSettingsCgm "github.com/tidepool-org/platform/data/types/settings/cgm" + + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" dataTypesSettingsCgmTest "github.com/tidepool-org/platform/data/types/settings/cgm/test" errorsTest "github.com/tidepool-org/platform/errors/test" logTest "github.com/tidepool-org/platform/log/test" @@ -22,34 +24,6 @@ var _ = Describe("ScheduledAlert", func() { Expect(dataTypesSettingsCgm.ScheduledAlertNameLengthMaximum).To(Equal(100)) }) - It("ScheduledAlertDaysSunday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysSunday).To(Equal("sunday")) - }) - - It("ScheduledAlertDaysMonday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysMonday).To(Equal("monday")) - }) - - It("ScheduledAlertDaysTuesday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysTuesday).To(Equal("tuesday")) - }) - - It("ScheduledAlertDaysWednesday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysWednesday).To(Equal("wednesday")) - }) - - It("ScheduledAlertDaysThursday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysThursday).To(Equal("thursday")) - }) - - It("ScheduledAlertDaysFriday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysFriday).To(Equal("friday")) - }) - - It("ScheduledAlertDaysSaturday is expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDaysSaturday).To(Equal("saturday")) - }) - It("ScheduledAlertStartMaximum is expected", func() { Expect(dataTypesSettingsCgm.ScheduledAlertStartMaximum).To(Equal(86400000)) }) @@ -66,10 +40,6 @@ var _ = Describe("ScheduledAlert", func() { Expect(dataTypesSettingsCgm.ScheduledAlertEndMinimum).To(Equal(0)) }) - It("ScheduledAlertDays returns expected", func() { - Expect(dataTypesSettingsCgm.ScheduledAlertDays()).To(Equal([]string{"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"})) - }) - Context("ParseScheduledAlerts", func() { // TODO }) @@ -205,20 +175,20 @@ var _ = Describe("ScheduledAlert", func() { ), Entry("days contains invalid", func(datum *dataTypesSettingsCgm.ScheduledAlert) { - datum.Days = pointer.FromStringArray(append([]string{"invalid"}, test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(0, len(dataTypesSettingsCgm.ScheduledAlertDays())-1, dataTypesSettingsCgm.ScheduledAlertDays())...)) + datum.Days = pointer.FromStringArray(append([]string{"invalid"}, test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(0, len(dataTypesCommon.DaysOfWeek())-1, dataTypesCommon.DaysOfWeek())...)) }, errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}), "/days/0"), ), Entry("days contains duplicate", func(datum *dataTypesSettingsCgm.ScheduledAlert) { - duplicate := test.RandomStringFromArray(dataTypesSettingsCgm.ScheduledAlertDays()) + duplicate := test.RandomStringFromArray(dataTypesCommon.DaysOfWeek()) datum.Days = pointer.FromStringArray([]string{duplicate, duplicate}) }, errorsTest.WithPointerSource(structureValidator.ErrorValueDuplicate(), "/days/1"), ), Entry("days valid", func(datum *dataTypesSettingsCgm.ScheduledAlert) { - datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesSettingsCgm.ScheduledAlertDays()), dataTypesSettingsCgm.ScheduledAlertDays())) + datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesCommon.DaysOfWeek()), dataTypesCommon.DaysOfWeek())) }, ), Entry("start missing", diff --git a/data/types/settings/cgm/test/scheduled_alert.go b/data/types/settings/cgm/test/scheduled_alert.go index 2ba30b0368..ac7af02a8e 100644 --- a/data/types/settings/cgm/test/scheduled_alert.go +++ b/data/types/settings/cgm/test/scheduled_alert.go @@ -1,6 +1,7 @@ package test import ( + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" dataTypesSettingsCgm "github.com/tidepool-org/platform/data/types/settings/cgm" "github.com/tidepool-org/platform/pointer" "github.com/tidepool-org/platform/test" @@ -39,7 +40,7 @@ func NewArrayFromScheduledAlerts(datum *dataTypesSettingsCgm.ScheduledAlerts, ob func RandomScheduledAlert() *dataTypesSettingsCgm.ScheduledAlert { datum := dataTypesSettingsCgm.NewScheduledAlert() datum.Name = pointer.FromString(test.RandomStringFromRange(1, dataTypesSettingsCgm.ScheduledAlertNameLengthMaximum)) - datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesSettingsCgm.ScheduledAlertDays()), dataTypesSettingsCgm.ScheduledAlertDays())) + datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesCommon.DaysOfWeek()), dataTypesCommon.DaysOfWeek())) datum.Start = pointer.FromInt(test.RandomIntFromRange(dataTypesSettingsCgm.ScheduledAlertStartMinimum, dataTypesSettingsCgm.ScheduledAlertStartMaximum)) datum.End = pointer.FromInt(test.RandomIntFromRange(dataTypesSettingsCgm.ScheduledAlertEndMinimum, dataTypesSettingsCgm.ScheduledAlertEndMaximum)) datum.Alerts = RandomAlerts() diff --git a/data/types/settings/pump/bolus.go b/data/types/settings/pump/bolus.go index 344d7f0a03..dc0cbbdcde 100644 --- a/data/types/settings/pump/bolus.go +++ b/data/types/settings/pump/bolus.go @@ -1,8 +1,11 @@ package pump import ( + "sort" + "github.com/tidepool-org/platform/data" "github.com/tidepool-org/platform/structure" + structureValidator "github.com/tidepool-org/platform/structure/validator" ) type Bolus struct { @@ -53,3 +56,63 @@ func (b *Bolus) Normalize(normalizer data.Normalizer) { b.Extended.Normalize(normalizer.WithReference("extended")) } } + +type BolusMap map[string]*Bolus + +func ParseBolusMap(parser structure.ObjectParser) *BolusMap { + if !parser.Exists() { + return nil + } + datum := NewBolusMap() + parser.Parse(datum) + return datum +} + +func NewBolusMap() *BolusMap { + return &BolusMap{} +} + +func (b *BolusMap) Parse(parser structure.ObjectParser) { + for _, reference := range parser.References() { + b.Set(reference, ParseBolus(parser.WithReferenceObjectParser(reference))) + } +} + +func (b *BolusMap) Validate(validator structure.Validator) { + for _, name := range b.sortedNames() { + datumValidator := validator.WithReference(name) + if datum := b.Get(name); datum != nil { + datum.Validate(datumValidator) + } else { + datumValidator.ReportError(structureValidator.ErrorValueNotExists()) + } + } +} + +func (b *BolusMap) Normalize(normalizer data.Normalizer) { + for _, name := range b.sortedNames() { + if datum := b.Get(name); datum != nil { + datum.Normalize(normalizer.WithReference(name)) + } + } +} + +func (b *BolusMap) Get(name string) *Bolus { + if datum, exists := (*b)[name]; exists { + return datum + } + return nil +} + +func (b *BolusMap) Set(name string, datum *Bolus) { + (*b)[name] = datum +} + +func (b *BolusMap) sortedNames() []string { + names := []string{} + for name := range *b { + names = append(names, name) + } + sort.Strings(names) + return names +} diff --git a/data/types/settings/pump/bolus_amount_maximum.go b/data/types/settings/pump/bolus_amount_maximum.go index 97e1ea5ba0..3549b52f50 100644 --- a/data/types/settings/pump/bolus_amount_maximum.go +++ b/data/types/settings/pump/bolus_amount_maximum.go @@ -9,7 +9,7 @@ import ( const ( BolusAmountMaximumUnitsUnits = "Units" - BolusAmountMaximumValueUnitsMaximum = 100.0 + BolusAmountMaximumValueUnitsMaximum = 250.0 BolusAmountMaximumValueUnitsMinimum = 0.0 ) diff --git a/data/types/settings/pump/bolus_amount_maximum_test.go b/data/types/settings/pump/bolus_amount_maximum_test.go index 4c26d04681..a1ca73215f 100644 --- a/data/types/settings/pump/bolus_amount_maximum_test.go +++ b/data/types/settings/pump/bolus_amount_maximum_test.go @@ -6,10 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - pumpTest "github.com/tidepool-org/platform/data/types/settings/pump/test" - dataNormalizer "github.com/tidepool-org/platform/data/normalizer" "github.com/tidepool-org/platform/data/types/settings/pump" + pumpTest "github.com/tidepool-org/platform/data/types/settings/pump/test" dataTypesTest "github.com/tidepool-org/platform/data/types/test" errorsTest "github.com/tidepool-org/platform/errors/test" logTest "github.com/tidepool-org/platform/log/test" @@ -24,7 +23,7 @@ var _ = Describe("BolusAmountMaximum", func() { }) It("BolusAmountMaximumValueUnitsMaximum is expected", func() { - Expect(pump.BolusAmountMaximumValueUnitsMaximum).To(Equal(100.0)) + Expect(pump.BolusAmountMaximumValueUnitsMaximum).To(Equal(250.0)) }) It("BolusAmountMaximumValueUnitsMinimum is expected", func() { @@ -71,7 +70,7 @@ var _ = Describe("BolusAmountMaximum", func() { Entry("units Units", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("Units") - datum.Value = pointer.FromFloat64(0.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum) }, ), Entry("units missing; value missing", @@ -85,28 +84,28 @@ var _ = Describe("BolusAmountMaximum", func() { Entry("units missing; value out of range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = nil - datum.Value = pointer.FromFloat64(-0.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum - 0.1) }, errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/units"), ), Entry("units missing; value in range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = nil - datum.Value = pointer.FromFloat64(0.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum) }, errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/units"), ), Entry("units missing; value in range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = nil - datum.Value = pointer.FromFloat64(100.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum) }, errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/units"), ), Entry("units missing; value out of range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = nil - datum.Value = pointer.FromFloat64(100.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum + 0.1) }, errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/units"), ), @@ -121,28 +120,28 @@ var _ = Describe("BolusAmountMaximum", func() { Entry("units invalid; value out of range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("invalid") - datum.Value = pointer.FromFloat64(-0.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum - 0.1) }, errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"Units"}), "/units"), ), Entry("units invalid; value in range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("invalid") - datum.Value = pointer.FromFloat64(0.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum) }, errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"Units"}), "/units"), ), Entry("units invalid; value in range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("invalid") - datum.Value = pointer.FromFloat64(100.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum) }, errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"Units"}), "/units"), ), Entry("units invalid; value out of range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("invalid") - datum.Value = pointer.FromFloat64(100.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum + 0.1) }, errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"Units"}), "/units"), ), @@ -156,28 +155,28 @@ var _ = Describe("BolusAmountMaximum", func() { Entry("units Units; value out of range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("Units") - datum.Value = pointer.FromFloat64(-0.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum - 0.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-0.1, 0.0, 100.0), "/value"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(pump.BolusAmountMaximumValueUnitsMinimum-0.1, pump.BolusAmountMaximumValueUnitsMinimum, pump.BolusAmountMaximumValueUnitsMaximum), "/value"), ), Entry("units Units; value in range (lower)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("Units") - datum.Value = pointer.FromFloat64(0.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMinimum) }, ), Entry("units Units; value in range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("Units") - datum.Value = pointer.FromFloat64(100.0) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum) }, ), Entry("units Units; value out of range (upper)", func(datum *pump.BolusAmountMaximum) { datum.Units = pointer.FromString("Units") - datum.Value = pointer.FromFloat64(100.1) + datum.Value = pointer.FromFloat64(pump.BolusAmountMaximumValueUnitsMaximum + 0.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(100.1, 0.0, 100.0), "/value"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(pump.BolusAmountMaximumValueUnitsMaximum+0.1, pump.BolusAmountMaximumValueUnitsMinimum, pump.BolusAmountMaximumValueUnitsMaximum), "/value"), ), Entry("multiple errors", func(datum *pump.BolusAmountMaximum) { @@ -233,8 +232,8 @@ var _ = Describe("BolusAmountMaximum", func() { It("returns expected range for units Units", func() { minimum, maximum := pump.BolusAmountMaximumValueRangeForUnits(pointer.FromString("Units")) - Expect(minimum).To(Equal(0.0)) - Expect(maximum).To(Equal(100.0)) + Expect(minimum).To(Equal(pump.BolusAmountMaximumValueUnitsMinimum)) + Expect(maximum).To(Equal(pump.BolusAmountMaximumValueUnitsMaximum)) }) }) }) diff --git a/data/types/settings/pump/bolus_test.go b/data/types/settings/pump/bolus_test.go index be6ef20581..546f280ca2 100644 --- a/data/types/settings/pump/bolus_test.go +++ b/data/types/settings/pump/bolus_test.go @@ -34,7 +34,7 @@ var _ = Describe("Bolus", func() { Context("Validate", func() { DescribeTable("validates the datum", func(mutator func(datum *pump.Bolus), expectedErrors ...error) { - datum := pumpTest.NewBolus() + datum := pumpTest.NewRandomBolus() mutator(datum) dataTypesTest.ValidateWithExpectedOrigins(datum, structure.Origins(), expectedErrors...) }, @@ -76,7 +76,7 @@ var _ = Describe("Bolus", func() { DescribeTable("normalizes the datum", func(mutator func(datum *pump.Bolus)) { for _, origin := range structure.Origins() { - datum := pumpTest.NewBolus() + datum := pumpTest.NewRandomBolus() mutator(datum) expectedDatum := pumpTest.CloneBolus(datum) normalizer := dataNormalizer.New(logTest.NewLogger()) @@ -99,4 +99,108 @@ var _ = Describe("Bolus", func() { ) }) }) + + Context("BolusMap", func() { + Context("Validate", func() { + DescribeTable("validates the datum", + func(mutator func(datum *pump.BolusMap), expectedErrors ...error) { + datum := pump.NewBolusMap() + mutator(datum) + dataTypesTest.ValidateWithExpectedOrigins(datum, structure.Origins(), expectedErrors...) + }, + Entry("succeeds", + func(datum *pump.BolusMap) {}, + ), + Entry("empty", + func(datum *pump.BolusMap) { + *datum = *pump.NewBolusMap() + }, + ), + + Entry("single invalid", + func(datum *pump.BolusMap) { + invalid := pumpTest.NewRandomBolus() + invalid.AmountMaximum.Units = nil + datum.Set("one", invalid) + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/one/amountMaximum/units"), + ), + Entry("single valid", + func(datum *pump.BolusMap) { + datum.Set("one", pumpTest.NewRandomBolus()) + }, + ), + Entry("multiple valid", + func(datum *pump.BolusMap) { + datum.Set("one", pumpTest.NewRandomBolus()) + datum.Set("two", pumpTest.NewRandomBolus()) + datum.Set("three", pumpTest.NewRandomBolus()) + }, + ), + Entry("multiple errors", + func(datum *pump.BolusMap) { + invalid := pumpTest.NewRandomBolus() + invalid.AmountMaximum.Units = nil + + invalidThree := pumpTest.NewRandomBolus() + invalidThree.AmountMaximum.Value = nil + + datum.Set("one", invalid) + datum.Set("two", pumpTest.NewRandomBolus()) + datum.Set("three", invalidThree) + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/one/amountMaximum/units"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/three/amountMaximum/value"), + ), + ) + }) + Context("Normalize", func() { + DescribeTable("normalizes the datum", + func(mutator func(datum *pump.BolusMap), expectator func(datum *pump.BolusMap, expectedDatum *pump.BolusMap)) { + for _, origin := range structure.Origins() { + datum := pumpTest.NewRandomBolusMap(1, 4) + mutator(datum) + expectedDatum := pumpTest.CloneBolusMap(datum) + normalizer := dataNormalizer.New(logTest.NewLogger()) + Expect(normalizer).ToNot(BeNil()) + datum.Normalize(normalizer.WithOrigin(origin)) + Expect(normalizer.Error()).To(BeNil()) + Expect(normalizer.Data()).To(BeEmpty()) + if expectator != nil { + expectator(datum, expectedDatum) + } + Expect(datum).To(Equal(expectedDatum)) + } + }, + Entry("does not modify the datum", + func(datum *pump.BolusMap) {}, + nil, + ), + Entry("does not modify the datum; amountMaximum missing", + func(datum *pump.BolusMap) { + for name := range *datum { + (*(*datum)[name]).AmountMaximum = nil + } + }, + nil, + ), + Entry("does not modify the datum; calculator missing", + func(datum *pump.BolusMap) { + for name := range *datum { + (*(*datum)[name]).Calculator = nil + } + }, + nil, + ), + Entry("does not modify the datum; extended missing", + func(datum *pump.BolusMap) { + for name := range *datum { + (*(*datum)[name]).Extended = nil + } + }, + nil, + ), + ) + }) + }) }) diff --git a/data/types/settings/pump/carbohydrate_ratio_start.go b/data/types/settings/pump/carbohydrate_ratio_start.go index de832d8176..b14baf32e7 100644 --- a/data/types/settings/pump/carbohydrate_ratio_start.go +++ b/data/types/settings/pump/carbohydrate_ratio_start.go @@ -11,7 +11,7 @@ import ( ) const ( - CarbohydrateRatioStartAmountMaximum = 250.0 + CarbohydrateRatioStartAmountMaximum = 500.0 CarbohydrateRatioStartAmountMinimum = 0.0 CarbohydrateRatioStartStartMaximum = 86400000 CarbohydrateRatioStartStartMinimum = 0 diff --git a/data/types/settings/pump/carbohydrate_ratio_start_test.go b/data/types/settings/pump/carbohydrate_ratio_start_test.go index 24f91dcf81..575b7e819b 100644 --- a/data/types/settings/pump/carbohydrate_ratio_start_test.go +++ b/data/types/settings/pump/carbohydrate_ratio_start_test.go @@ -17,7 +17,7 @@ import ( var _ = Describe("CarbohydrateRatioStart", func() { It("CarbohydrateRatioStartAmountMaximum is expected", func() { - Expect(pump.CarbohydrateRatioStartAmountMaximum).To(Equal(250.0)) + Expect(pump.CarbohydrateRatioStartAmountMaximum).To(Equal(500.0)) }) It("CarbohydrateRatioStartAmountMinimum is expected", func() { @@ -63,17 +63,21 @@ var _ = Describe("CarbohydrateRatioStart", func() { ), Entry("amount out of range (lower)", func(datum *pump.CarbohydrateRatioStart) { datum.Amount = pointer.FromFloat64(-0.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-0.1, 0, 250), "/amount"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-0.1, 0, pump.CarbohydrateRatioStartAmountMaximum), "/amount"), ), Entry("amount in range (lower)", func(datum *pump.CarbohydrateRatioStart) { datum.Amount = pointer.FromFloat64(0.0) }, ), Entry("amount in range (upper)", - func(datum *pump.CarbohydrateRatioStart) { datum.Amount = pointer.FromFloat64(250.0) }, + func(datum *pump.CarbohydrateRatioStart) { + datum.Amount = pointer.FromFloat64(pump.CarbohydrateRatioStartAmountMaximum) + }, ), Entry("amount out of range (upper)", - func(datum *pump.CarbohydrateRatioStart) { datum.Amount = pointer.FromFloat64(250.1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(250.1, 0, 250), "/amount"), + func(datum *pump.CarbohydrateRatioStart) { + datum.Amount = pointer.FromFloat64(pump.CarbohydrateRatioStartAmountMaximum + 0.1) + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(pump.CarbohydrateRatioStartAmountMaximum+0.1, 0, pump.CarbohydrateRatioStartAmountMaximum), "/amount"), ), Entry("start missing", func(datum *pump.CarbohydrateRatioStart) { datum.Start = nil }, diff --git a/data/types/settings/pump/override_preset.go b/data/types/settings/pump/override_preset.go index 8e8148fc8b..df14a81552 100644 --- a/data/types/settings/pump/override_preset.go +++ b/data/types/settings/pump/override_preset.go @@ -15,7 +15,7 @@ const ( BasalRateScaleFactorMinimum = 0.1 CarbohydrateRatioScaleFactorMaximum = 10.0 CarbohydrateRatioScaleFactorMinimum = 0.1 - DurationMaximum = 604800 // 7 days in seconds + DurationMaximum = 604800000 // 7 days in milliseconds DurationMinimum = 0 InsulinSensitivityScaleFactorMaximum = 10.0 InsulinSensitivityScaleFactorMinimum = 0.1 diff --git a/data/types/settings/pump/override_preset_test.go b/data/types/settings/pump/override_preset_test.go index d328fbc502..afc61c85ba 100644 --- a/data/types/settings/pump/override_preset_test.go +++ b/data/types/settings/pump/override_preset_test.go @@ -41,7 +41,7 @@ var _ = Describe("OverridePreset", func() { }) It("DurationMaximum is expected", func() { - Expect(dataTypesSettingsPump.DurationMaximum).To(Equal(604800)) + Expect(dataTypesSettingsPump.DurationMaximum).To(Equal(604800000)) }) It("DurationMinimum is expected", func() { @@ -193,7 +193,7 @@ var _ = Describe("OverridePreset", func() { func(datum *dataTypesSettingsPump.OverridePreset, unitsBloodGlucose *string) { datum.Duration = pointer.FromInt(-1) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration"), ), Entry("duration; in range (lower)", pointer.FromString("mmol/L"), @@ -204,15 +204,15 @@ var _ = Describe("OverridePreset", func() { Entry("duration; in range (upper)", pointer.FromString("mmol/L"), func(datum *dataTypesSettingsPump.OverridePreset, unitsBloodGlucose *string) { - datum.Duration = pointer.FromInt(604800) + datum.Duration = pointer.FromInt(604800000) }, ), Entry("duration; out of range (upper)", pointer.FromString("mmol/L"), func(datum *dataTypesSettingsPump.OverridePreset, unitsBloodGlucose *string) { - datum.Duration = pointer.FromInt(604801) + datum.Duration = pointer.FromInt(604800001) }, - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(604801, 0, 604800), "/duration"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(604800001, 0, 604800000), "/duration"), ), Entry("units mmol/L; blood glucose target missing", pointer.FromString("mmol/L"), @@ -389,7 +389,7 @@ var _ = Describe("OverridePreset", func() { datum.InsulinSensitivityScaleFactor = pointer.FromFloat64(0.09) }, errorsTest.WithPointerSource(structureValidator.ErrorValueEmpty(), "/abbreviation"), - errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-1, 0, 604800), "/duration"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(-1, 0, 604800000), "/duration"), errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/basalRateScaleFactor"), errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/carbRatioScaleFactor"), errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange(0.09, 0.1, 10.0), "/insulinSensitivityScaleFactor"), diff --git a/data/types/settings/pump/pump.go b/data/types/settings/pump/pump.go index eb61abdfb0..9afccb8dfe 100644 --- a/data/types/settings/pump/pump.go +++ b/data/types/settings/pump/pump.go @@ -42,6 +42,7 @@ type Pump struct { BloodGlucoseTargetSchedule *BloodGlucoseTargetStartArray `json:"bgTarget,omitempty" bson:"bgTarget,omitempty"` // TODO: Move into BolusCalculator struct; rename bloodGlucoseTarget BloodGlucoseTargetSchedules *BloodGlucoseTargetStartArrayMap `json:"bgTargets,omitempty" bson:"bgTargets,omitempty"` // TODO: Move into BolusCalculator struct; rename bloodGlucoseTargets Bolus *Bolus `json:"bolus,omitempty" bson:"bolus,omitempty"` + Boluses *BolusMap `json:"boluses,omitempty" bson:"boluses,omitempty"` CarbohydrateRatioSchedule *CarbohydrateRatioStartArray `json:"carbRatio,omitempty" bson:"carbRatio,omitempty"` // TODO: Move into BolusCalculator struct; rename carbohydrateRatio CarbohydrateRatioSchedules *CarbohydrateRatioStartArrayMap `json:"carbRatios,omitempty" bson:"carbRatios,omitempty"` // TODO: Move into BolusCalculator struct; rename carbohydrateRatios Display *Display `json:"display,omitempty" bson:"display,omitempty"` @@ -57,6 +58,7 @@ type Pump struct { OverridePresets *OverridePresetMap `json:"overridePresets,omitempty" bson:"overridePresets,omitempty"` ScheduleTimeZoneOffset *int `json:"scheduleTimeZoneOffset,omitempty" bson:"scheduleTimeZoneOffset,omitempty"` SerialNumber *string `json:"serialNumber,omitempty" bson:"serialNumber,omitempty"` + SleepSchedules *SleepScheduleMap `json:"sleepSchedules,omitempty" bson:"sleepSchedules,omitempty"` SoftwareVersion *string `json:"softwareVersion,omitempty" bson:"softwareVersion,omitempty"` Units *Units `json:"units,omitempty" bson:"units,omitempty"` // TODO: Move into appropriate structs } @@ -85,6 +87,7 @@ func (p *Pump) Parse(parser structure.ObjectParser) { p.BloodGlucoseTargetSchedule = ParseBloodGlucoseTargetStartArray(parser.WithReferenceArrayParser("bgTarget")) p.BloodGlucoseTargetSchedules = ParseBloodGlucoseTargetStartArrayMap(parser.WithReferenceObjectParser("bgTargets")) p.Bolus = ParseBolus(parser.WithReferenceObjectParser("bolus")) + p.Boluses = ParseBolusMap(parser.WithReferenceObjectParser("boluses")) p.CarbohydrateRatioSchedule = ParseCarbohydrateRatioStartArray(parser.WithReferenceArrayParser("carbRatio")) p.CarbohydrateRatioSchedules = ParseCarbohydrateRatioStartArrayMap(parser.WithReferenceObjectParser("carbRatios")) p.Display = ParseDisplay(parser.WithReferenceObjectParser("display")) @@ -99,6 +102,7 @@ func (p *Pump) Parse(parser structure.ObjectParser) { p.Name = parser.String("name") p.OverridePresets = ParseOverridePresetMap(parser.WithReferenceObjectParser("overridePresets")) p.ScheduleTimeZoneOffset = parser.Int("scheduleTimeZoneOffset") + p.SleepSchedules = ParseSleepScheduleMap(parser.WithReferenceObjectParser("sleepSchedules")) p.SerialNumber = parser.String("serialNumber") p.SoftwareVersion = parser.String("softwareVersion") p.Units = ParseUnits(parser.WithReferenceObjectParser("units")) @@ -147,9 +151,16 @@ func (p *Pump) Validate(validator structure.Validator) { } else if p.BloodGlucoseTargetSchedules != nil { p.BloodGlucoseTargetSchedules.Validate(validator.WithReference("bgTargets"), unitsBloodGlucose) } + if p.Bolus != nil { p.Bolus.Validate(validator.WithReference("bolus")) + if p.Boluses != nil { + validator.WithReference("boluses").ReportError(structureValidator.ErrorValueExists()) + } + } else if p.Boluses != nil { + p.Boluses.Validate(validator.WithReference("boluses")) } + if p.CarbohydrateRatioSchedule != nil { p.CarbohydrateRatioSchedule.Validate(validator.WithReference("carbRatio")) if p.CarbohydrateRatioSchedules != nil { @@ -185,6 +196,9 @@ func (p *Pump) Validate(validator structure.Validator) { if p.OverridePresets != nil { p.OverridePresets.Validate(validator.WithReference("overridePresets"), unitsBloodGlucose) } + if p.SleepSchedules != nil { + p.SleepSchedules.Validate(validator.WithReference("sleepSchedules")) + } validator.Int("scheduleTimeZoneOffset", p.ScheduleTimeZoneOffset).InRange(ScheduleTimeZoneOffsetMinimum, ScheduleTimeZoneOffsetMaximum) validator.String("serialNumber", p.SerialNumber).NotEmpty().LengthLessThanOrEqualTo(SerialNumberLengthMaximum) validator.String("softwareVersion", p.SoftwareVersion).NotEmpty().LengthLessThanOrEqualTo(SoftwareVersionLengthMaximum) @@ -232,6 +246,9 @@ func (p *Pump) Normalize(normalizer data.Normalizer) { if p.Bolus != nil { p.Bolus.Normalize(normalizer.WithReference("bolus")) } + if p.Boluses != nil { + p.Boluses.Normalize(normalizer.WithReference("boluses")) + } if p.CarbohydrateRatioSchedule != nil { p.CarbohydrateRatioSchedule.Normalize(normalizer.WithReference("carbRatio")) } @@ -258,6 +275,9 @@ func (p *Pump) Normalize(normalizer data.Normalizer) { if p.OverridePresets != nil { p.OverridePresets.Normalize(normalizer.WithReference("overridePresets"), unitsBloodGlucose) } + if p.SleepSchedules != nil { + p.SleepSchedules.Normalize(normalizer.WithReference("sleepSchedules")) + } if p.Units != nil { p.Units.Normalize(normalizer.WithReference("units")) } diff --git a/data/types/settings/pump/pump_test.go b/data/types/settings/pump/pump_test.go index c1a5702a5a..7457078f23 100644 --- a/data/types/settings/pump/pump_test.go +++ b/data/types/settings/pump/pump_test.go @@ -1,6 +1,7 @@ package pump_test import ( + "fmt" "sort" . "github.com/onsi/ginkgo/v2" @@ -44,6 +45,7 @@ var _ = Describe("Pump", func() { Expect(datum.BloodGlucoseTargetSchedule).To(BeNil()) Expect(datum.BloodGlucoseTargetSchedules).To(BeNil()) Expect(datum.Bolus).To(BeNil()) + Expect(datum.Boluses).To(BeNil()) Expect(datum.CarbohydrateRatioSchedule).To(BeNil()) Expect(datum.CarbohydrateRatioSchedules).To(BeNil()) Expect(datum.Display).To(BeNil()) @@ -58,6 +60,7 @@ var _ = Describe("Pump", func() { Expect(datum.Name).To(BeNil()) Expect(datum.OverridePresets).To(BeNil()) Expect(datum.ScheduleTimeZoneOffset).To(BeNil()) + Expect(datum.SleepSchedules).To(BeNil()) Expect(datum.SerialNumber).To(BeNil()) Expect(datum.SoftwareVersion).To(BeNil()) Expect(datum.Units).To(BeNil()) @@ -279,16 +282,51 @@ var _ = Describe("Pump", func() { ), Entry("bolus missing", pointer.FromString("mmol/L"), - func(datum *pump.Pump, unitsBloodGlucose *string) { datum.Bolus = nil }, + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.Bolus = nil + }, ), Entry("bolus invalid", pointer.FromString("mmol/L"), - func(datum *pump.Pump, unitsBloodGlucose *string) { datum.Bolus.Extended.Enabled = nil }, - errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/bolus/extended/enabled", pumpTest.NewMeta()), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.Bolus = pumpTest.NewRandomBolus() + datum.Bolus.Calculator.Enabled = nil + datum.Boluses = nil + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), "/bolus/calculator/enabled", pumpTest.NewMeta()), ), Entry("bolus valid", pointer.FromString("mmol/L"), - func(datum *pump.Pump, unitsBloodGlucose *string) { datum.Bolus = pumpTest.NewBolus() }, + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.Bolus = pumpTest.NewRandomBolus() + datum.Boluses = nil + }, + ), + Entry("boluses missing", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { datum.Boluses = nil }, + ), + Entry("boluses invalid", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.Bolus = nil + datum.Boluses = pumpTest.NewRandomBolusMap(1, 1) + for _, v := range *datum.Boluses { + v.AmountMaximum.Units = nil + v.Extended.Enabled = nil + v.Calculator.Enabled = nil + } + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), fmt.Sprintf("/boluses/%s/amountMaximum/units", pumpTest.BolusName(1)), pumpTest.NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), fmt.Sprintf("/boluses/%s/calculator/enabled", pumpTest.BolusName(1)), pumpTest.NewMeta()), + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotExists(), fmt.Sprintf("/boluses/%s/extended/enabled", pumpTest.BolusName(1)), pumpTest.NewMeta()), + ), + Entry("boluses valid", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.Bolus = nil + datum.Boluses = pumpTest.NewRandomBolusMap(1, 5) + }, ), Entry("carbohydrate ratio schedule and carbohydrate ratio schedules missing", pointer.FromString("mmol/L"), @@ -618,6 +656,37 @@ var _ = Describe("Pump", func() { }, errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorLengthNotLessThanOrEqualTo(101, 100), "/serialNumber", pumpTest.NewMeta()), ), + Entry("sleep schedules missing", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.SleepSchedules = nil + }, + ), + Entry("sleep schedules empty", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.SleepSchedules = pump.NewSleepScheduleMap() + }, + ), + Entry("sleep schedules invalid", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.SleepSchedules = pumpTest.RandomSleepScheduleMap(2) + (*datum.SleepSchedules)[pumpTest.SleepScheduleName(1)].Start = pointer.FromInt(pump.SleepSchedulesMidnightOffsetMaximum / 2) + (*datum.SleepSchedules)[pumpTest.SleepScheduleName(1)].End = pointer.FromInt(pump.SleepSchedulesMidnightOffsetMaximum + 1) + }, + errorsTest.WithPointerSourceAndMeta(structureValidator.ErrorValueNotInRange( + pump.SleepSchedulesMidnightOffsetMaximum+1, + pump.SleepSchedulesMidnightOffsetMaximum/2, + pump.SleepSchedulesMidnightOffsetMaximum), + fmt.Sprintf("/sleepSchedules/%s/end", pumpTest.SleepScheduleName(1)), pumpTest.NewMeta()), + ), + Entry("sleep schedules valid", + pointer.FromString("mmol/L"), + func(datum *pump.Pump, unitsBloodGlucose *string) { + datum.SleepSchedules = pumpTest.RandomSleepScheduleMap(3) + }, + ), Entry("software version missing", pointer.FromString("mmol/L"), func(datum *pump.Pump, units *string) { datum.SoftwareVersion = nil }, @@ -673,8 +742,9 @@ var _ = Describe("Pump", func() { datum.BloodGlucoseTargetSchedules = nil datum.BloodGlucoseTargetPhysicalActivity = dataBloodGlucose.NewTarget() datum.BloodGlucoseTargetPreprandial = dataBloodGlucose.NewTarget() - datum.BloodGlucoseTargetSchedules = nil + datum.Bolus = pumpTest.NewRandomBolus() datum.Bolus.Extended.Enabled = nil + datum.Boluses = nil invalidCarbohydrateRatioSchedule := pumpTest.NewCarbohydrateRatioStartArray() (*invalidCarbohydrateRatioSchedule)[0].Start = nil datum.CarbohydrateRatioSchedule = invalidCarbohydrateRatioSchedule diff --git a/data/types/settings/pump/sleep_schedule.go b/data/types/settings/pump/sleep_schedule.go new file mode 100644 index 0000000000..e1da568047 --- /dev/null +++ b/data/types/settings/pump/sleep_schedule.go @@ -0,0 +1,123 @@ +package pump + +import ( + "sort" + + "github.com/tidepool-org/platform/data" + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" + "github.com/tidepool-org/platform/structure" + structureValidator "github.com/tidepool-org/platform/structure/validator" +) + +const ( + SleepSchedulesMidnightOffsetMaximum = 86400 + SleepSchedulesMidnightOffsetMinimum = 0 +) + +type SleepScheduleMap map[string]*SleepSchedule + +func ParseSleepScheduleMap(parser structure.ObjectParser) *SleepScheduleMap { + if !parser.Exists() { + return nil + } + datum := NewSleepScheduleMap() + parser.Parse(datum) + return datum +} + +func NewSleepScheduleMap() *SleepScheduleMap { + return &SleepScheduleMap{} +} + +func (s *SleepScheduleMap) Parse(parser structure.ObjectParser) { + for _, reference := range parser.References() { + s.Set(reference, ParseSleepSchedule(parser.WithReferenceObjectParser(reference))) + } +} + +func (s *SleepScheduleMap) Validate(validator structure.Validator) { + for _, name := range s.sortedNames() { + datumValidator := validator.WithReference(name) + if datum := s.Get(name); datum != nil { + datum.Validate(datumValidator) + } else { + datumValidator.ReportError(structureValidator.ErrorValueNotExists()) + } + } +} + +func (s *SleepScheduleMap) Normalize(normalizer data.Normalizer) { + for _, name := range s.sortedNames() { + datumNormalizer := normalizer.WithReference(name) + if datum := s.Get(name); datum != nil { + datum.Normalize(datumNormalizer) + } + } +} + +func (s *SleepScheduleMap) Get(name string) *SleepSchedule { + if datum, exists := (*s)[name]; exists { + return datum + } + return nil +} + +func (s *SleepScheduleMap) Set(name string, datum *SleepSchedule) { + (*s)[name] = datum +} + +func (s *SleepScheduleMap) sortedNames() []string { + names := []string{} + for name := range *s { + names = append(names, name) + } + sort.Strings(names) + return names +} + +type SleepSchedule struct { + Enabled *bool `json:"enabled,omitempty" bson:"enabled,omitempty"` + Days *[]string `json:"days,omitempty" bson:"days,omitempty"` + Start *int `json:"start,omitempty" bson:"start,omitempty"` + End *int `json:"end,omitempty" bson:"end,omitempty"` +} + +func ParseSleepSchedule(parser structure.ObjectParser) *SleepSchedule { + if !parser.Exists() { + return nil + } + datum := NewSleepSchedule() + parser.Parse(datum) + return datum +} + +func NewSleepSchedule() *SleepSchedule { + return &SleepSchedule{} +} + +func (s *SleepSchedule) Parse(parser structure.ObjectParser) { + s.Enabled = parser.Bool("enabled") + s.Days = parser.StringArray("days") + s.Start = parser.Int("start") + s.End = parser.Int("end") +} + +func (s *SleepSchedule) Validate(validator structure.Validator) { + validator.Bool("enabled", s.Enabled).Exists() + if s.Enabled != nil && *s.Enabled { + validator.StringArray("days", s.Days).Exists().EachUsing(dataTypesCommon.DayOfWeekValidator).EachUnique() + validator.Int("start", s.Start).Exists().InRange(SleepSchedulesMidnightOffsetMinimum, SleepSchedulesMidnightOffsetMaximum) + + if endValidator := validator.Int("end", s.End); s.Start != nil { + endValidator.Exists().InRange(*s.Start, SleepSchedulesMidnightOffsetMaximum) + } else { + endValidator.Exists().InRange(SleepSchedulesMidnightOffsetMinimum, SleepSchedulesMidnightOffsetMaximum) + } + } +} + +func (s *SleepSchedule) Normalize(normalizer data.Normalizer) { + if s.Days != nil { + sort.Sort(dataTypesCommon.DaysOfWeekByDayIndex(*s.Days)) + } +} diff --git a/data/types/settings/pump/sleep_schedule_test.go b/data/types/settings/pump/sleep_schedule_test.go new file mode 100644 index 0000000000..f63bc026bc --- /dev/null +++ b/data/types/settings/pump/sleep_schedule_test.go @@ -0,0 +1,241 @@ +package pump_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" + dataTypesSettingsPump "github.com/tidepool-org/platform/data/types/settings/pump" + dataTypesSettingsPumpTest "github.com/tidepool-org/platform/data/types/settings/pump/test" + errorsTest "github.com/tidepool-org/platform/errors/test" + logTest "github.com/tidepool-org/platform/log/test" + "github.com/tidepool-org/platform/pointer" + structureValidator "github.com/tidepool-org/platform/structure/validator" + "github.com/tidepool-org/platform/test" +) + +var _ = Describe("SleepSchedule", func() { + + Context("NewSleepSchedules", func() { + It("returns successfully with default values", func() { + datum := dataTypesSettingsPump.NewSleepScheduleMap() + Expect(datum).ToNot(BeNil()) + Expect(*datum).To(BeEmpty()) + }) + }) + + Context("SleepSchedules", func() { + + Context("Validate", func() { + DescribeTable("validates the datum", + func(mutator func(datum *dataTypesSettingsPump.SleepScheduleMap), expectedErrors ...error) { + datum := dataTypesSettingsPumpTest.RandomSleepScheduleMap(3) + mutator(datum) + errorsTest.ExpectEqual(structureValidator.New(logTest.NewLogger()).Validate(datum), expectedErrors...) + }, + Entry("succeeds", + func(datum *dataTypesSettingsPump.SleepScheduleMap) {}, + ), + Entry("empty", + func(datum *dataTypesSettingsPump.SleepScheduleMap) { + *datum = *dataTypesSettingsPump.NewSleepScheduleMap() + }, + ), + Entry("has one", + func(datum *dataTypesSettingsPump.SleepScheduleMap) { + *datum = *dataTypesSettingsPumpTest.RandomSleepScheduleMap(1) + }, + ), + Entry("has many", + func(datum *dataTypesSettingsPump.SleepScheduleMap) { + *datum = *dataTypesSettingsPumpTest.RandomSleepScheduleMap(3) + }, + ), + Entry("entry missing", + func(datum *dataTypesSettingsPump.SleepScheduleMap) { + (*datum)[dataTypesSettingsPumpTest.SleepScheduleName(0)] = nil + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), fmt.Sprintf("/%s", dataTypesSettingsPumpTest.SleepScheduleName(0))), + ), + Entry("multiple errors", + func(datum *dataTypesSettingsPump.SleepScheduleMap) { + *datum = *dataTypesSettingsPumpTest.RandomSleepScheduleMap(3) + (*datum)[dataTypesSettingsPumpTest.SleepScheduleName(1)] = nil + }, + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), fmt.Sprintf("/%s", dataTypesSettingsPumpTest.SleepScheduleName(1))), + ), + ) + }) + }) + + Context("NewSleepSchedule", func() { + It("returns successfully with default values", func() { + datum := dataTypesSettingsPump.NewSleepSchedule() + Expect(datum).ToNot(BeNil()) + Expect(datum.Enabled).To(BeNil()) + Expect(datum.Days).To(BeNil()) + Expect(datum.Start).To(BeNil()) + Expect(datum.End).To(BeNil()) + }) + }) + + Context("SleepSchedule", func() { + DescribeTable("serializes the datum as expected", + func(mutator func(datum *dataTypesSettingsPump.SleepSchedule)) { + datum := dataTypesSettingsPumpTest.RandomSleepSchedule() + mutator(datum) + test.ExpectSerializedObjectBSON(datum, dataTypesSettingsPumpTest.NewObjectFromSleepSchedule(datum, test.ObjectFormatBSON)) + test.ExpectSerializedObjectJSON(datum, dataTypesSettingsPumpTest.NewObjectFromSleepSchedule(datum, test.ObjectFormatJSON)) + }, + Entry("succeeds", + func(datum *dataTypesSettingsPump.SleepSchedule) {}, + ), + Entry("empty", + func(datum *dataTypesSettingsPump.SleepSchedule) { *datum = dataTypesSettingsPump.SleepSchedule{} }, + ), + ) + + Context("Validate", func() { + DescribeTable("validates the datum", + func(mutator func(datum *dataTypesSettingsPump.SleepSchedule) []error) { + datum := dataTypesSettingsPumpTest.RandomSleepSchedule() + expectedErrors := mutator(datum) + errorsTest.ExpectEqual(structureValidator.New(logTest.NewLogger()).Validate(datum), expectedErrors...) + }, + Entry("succeeds", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { return nil }, + ), + Entry("enabled missing", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Enabled = nil + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/enabled")} + }, + ), + Entry("days missing", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Days = nil + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/days")} + }, + ), + Entry("days contains invalid", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Days = pointer.FromStringArray(append([]string{"invalid"}, test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(0, len(dataTypesCommon.DaysOfWeek())-1, dataTypesCommon.DaysOfWeek())...)) + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueStringNotOneOf("invalid", []string{"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}), "/days/0")} + }, + ), + Entry("days contains duplicate", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + duplicate := test.RandomStringFromArray(dataTypesCommon.DaysOfWeek()) + datum.Days = pointer.FromStringArray([]string{duplicate, duplicate}) + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueDuplicate(), "/days/1")} + + }, + ), + Entry("days valid", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesCommon.DaysOfWeek()), dataTypesCommon.DaysOfWeek())) + return nil + }, + ), + Entry("start missing", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Start = nil + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/start")} + }, + ), + Entry("start out of range (lower)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Start = pointer.FromInt(-1) + return []error{ + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange( + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum-1, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum), "/start"), + } + + }, + ), + Entry("start in range (lower)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Start = pointer.FromInt(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum) + return nil + }, + ), + Entry("start in range (upper)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Start = pointer.FromInt(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum - 1) + datum.End = pointer.FromInt(*datum.Start + 1) + return nil + }, + ), + Entry("start out of range (upper)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Start = pointer.FromInt(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum + 1) + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange( + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum+1, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum), "/start"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange( + *datum.End, + *datum.Start, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum), "/end"), + } + }, + ), + Entry("end missing", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.End = nil + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/end")} + }, + ), + Entry("end out of range (lower)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.End = pointer.FromInt(-1) + return []error{errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange( + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum-1, + *datum.Start, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum), "/end")} + }, + ), + Entry("end in range (lower)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.End = pointer.FromInt(*datum.Start) + return nil + }, + ), + Entry("end in range (upper)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.End = pointer.FromInt(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum) + return nil + }, + ), + Entry("end out of range (upper)", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.End = pointer.FromInt(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum + 1) + return []error{ + errorsTest.WithPointerSource(structureValidator.ErrorValueNotInRange( + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum+1, + *datum.Start, + dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum), "/end"), + } + }, + ), + Entry("multiple errors", + func(datum *dataTypesSettingsPump.SleepSchedule) []error { + datum.Days = nil + datum.Start = nil + datum.End = nil + + return []error{ + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/days"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/start"), + errorsTest.WithPointerSource(structureValidator.ErrorValueNotExists(), "/end"), + } + }, + ), + ) + }) + }) +}) diff --git a/data/types/settings/pump/test/bolus.go b/data/types/settings/pump/test/bolus.go index 105d990b99..7fe52a4562 100644 --- a/data/types/settings/pump/test/bolus.go +++ b/data/types/settings/pump/test/bolus.go @@ -1,20 +1,56 @@ package test -import "github.com/tidepool-org/platform/data/types/settings/pump" +import ( + "fmt" -func NewBolus() *pump.Bolus { - datum := pump.NewBolus() + dataTypesSettingsPump "github.com/tidepool-org/platform/data/types/settings/pump" + "github.com/tidepool-org/platform/test" +) + +func NewRandomBolus() *dataTypesSettingsPump.Bolus { + datum := dataTypesSettingsPump.NewBolus() datum.AmountMaximum = NewBolusAmountMaximum() datum.Extended = NewBolusExtended() + datum.Calculator = NewBolusCalculator() return datum } -func CloneBolus(datum *pump.Bolus) *pump.Bolus { +func CloneBolus(datum *dataTypesSettingsPump.Bolus) *dataTypesSettingsPump.Bolus { if datum == nil { return nil } - clone := pump.NewBolus() + clone := dataTypesSettingsPump.NewBolus() clone.AmountMaximum = CloneBolusAmountMaximum(datum.AmountMaximum) clone.Extended = CloneBolusExtended(datum.Extended) + clone.Calculator = CloneBolusCalculator(datum.Calculator) + return clone +} + +func BolusName(index int) string { + return fmt.Sprintf("bolus-%d", index) +} + +func NewRandomBolusMap(minimumLength int, maximumLength int) *dataTypesSettingsPump.BolusMap { + datum := dataTypesSettingsPump.NewBolusMap() + count := test.RandomIntFromRange(minimumLength, maximumLength) + if count == 0 { + return datum + } + + for i := 0; i < count; i++ { + datum.Set(BolusName(count), NewRandomBolus()) + } + + return datum +} + +func CloneBolusMap(datum *dataTypesSettingsPump.BolusMap) *dataTypesSettingsPump.BolusMap { + if datum == nil { + return nil + } + clone := dataTypesSettingsPump.NewBolusMap() + for k, v := range *datum { + (*clone)[k] = CloneBolus(v) + } return clone } diff --git a/data/types/settings/pump/test/pump.go b/data/types/settings/pump/test/pump.go index 39992eae95..07645b70ef 100644 --- a/data/types/settings/pump/test/pump.go +++ b/data/types/settings/pump/test/pump.go @@ -48,7 +48,7 @@ func NewPump(unitsBloodGlucose *string) *pump.Pump { datum.BloodGlucoseTargetPreprandial = dataBloodGlucoseTest.RandomTarget(unitsBloodGlucose) datum.BloodGlucoseTargetSchedules = pump.NewBloodGlucoseTargetStartArrayMap() datum.BloodGlucoseTargetSchedules.Set(scheduleName, RandomBloodGlucoseTargetStartArray(unitsBloodGlucose)) - datum.Bolus = NewBolus() + datum.Bolus = NewRandomBolus() datum.CarbohydrateRatioSchedules = pump.NewCarbohydrateRatioStartArrayMap() datum.CarbohydrateRatioSchedules.Set(scheduleName, NewCarbohydrateRatioStartArray()) datum.Display = NewDisplay() @@ -86,6 +86,7 @@ func ClonePump(datum *pump.Pump) *pump.Pump { clone.BloodGlucoseTargetSchedule = CloneBloodGlucoseTargetStartArray(datum.BloodGlucoseTargetSchedule) clone.BloodGlucoseTargetSchedules = CloneBloodGlucoseTargetStartArrayMap(datum.BloodGlucoseTargetSchedules) clone.Bolus = CloneBolus(datum.Bolus) + clone.Boluses = CloneBolusMap(datum.Boluses) clone.CarbohydrateRatioSchedule = CloneCarbohydrateRatioStartArray(datum.CarbohydrateRatioSchedule) clone.CarbohydrateRatioSchedules = CloneCarbohydrateRatioStartArrayMap(datum.CarbohydrateRatioSchedules) clone.Display = CloneDisplay(datum.Display) diff --git a/data/types/settings/pump/test/sleep_schedule.go b/data/types/settings/pump/test/sleep_schedule.go new file mode 100644 index 0000000000..be6fe7e6e4 --- /dev/null +++ b/data/types/settings/pump/test/sleep_schedule.go @@ -0,0 +1,87 @@ +package test + +import ( + "fmt" + + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" + dataTypesSettingsPump "github.com/tidepool-org/platform/data/types/settings/pump" + "github.com/tidepool-org/platform/pointer" + "github.com/tidepool-org/platform/test" +) + +func SleepScheduleName(index int) string { + return fmt.Sprintf("schedule-%d", index) +} + +func RandomSleepScheduleMap(count int) *dataTypesSettingsPump.SleepScheduleMap { + datum := dataTypesSettingsPump.NewSleepScheduleMap() + for i := 0; i < count; i++ { + (*datum)[SleepScheduleName(i)] = RandomSleepSchedule() + } + return datum +} + +func CloneSleepScheduleMap(datum *dataTypesSettingsPump.SleepScheduleMap) *dataTypesSettingsPump.SleepScheduleMap { + if datum == nil { + return nil + } + clone := make(dataTypesSettingsPump.SleepScheduleMap, len(*datum)) + for k, v := range *datum { + clone[k] = CloneSleepSchedule(v) + } + return &clone +} + +func NewObjectFromSleepScheduleMap(datum *dataTypesSettingsPump.SleepScheduleMap, objectFormat test.ObjectFormat) map[string]interface{} { + if datum == nil { + return nil + } + object := map[string]interface{}{} + for k, v := range *datum { + object[k] = NewObjectFromSleepSchedule(v, objectFormat) + } + return object +} + +func RandomSleepSchedule() *dataTypesSettingsPump.SleepSchedule { + datum := dataTypesSettingsPump.NewSleepSchedule() + // enabled by default, if not enabled days, start and end not required + datum.Enabled = pointer.FromBool(true) + datum.Days = pointer.FromStringArray(test.RandomStringArrayFromRangeAndArrayWithoutDuplicates(1, len(dataTypesCommon.DaysOfWeek()), dataTypesCommon.DaysOfWeek())) + datum.Start = pointer.FromInt(test.RandomIntFromRange(dataTypesSettingsPump.SleepSchedulesMidnightOffsetMinimum, dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum)) + datum.End = pointer.FromInt(test.RandomIntFromRange(*datum.Start, dataTypesSettingsPump.SleepSchedulesMidnightOffsetMaximum)) + return datum +} + +func CloneSleepSchedule(datum *dataTypesSettingsPump.SleepSchedule) *dataTypesSettingsPump.SleepSchedule { + if datum == nil { + return nil + } + clone := dataTypesSettingsPump.NewSleepSchedule() + clone.Enabled = pointer.CloneBool(datum.Enabled) + clone.Days = pointer.CloneStringArray(datum.Days) + clone.Start = pointer.CloneInt(datum.Start) + clone.End = pointer.CloneInt(datum.End) + return clone +} + +func NewObjectFromSleepSchedule(datum *dataTypesSettingsPump.SleepSchedule, objectFormat test.ObjectFormat) map[string]interface{} { + if datum == nil { + return nil + } + object := map[string]interface{}{} + if datum.Enabled != nil { + object["enabled"] = test.NewObjectFromBool(*datum.Enabled, objectFormat) + } + if datum.Days != nil { + object["days"] = test.NewObjectFromStringArray(*datum.Days, objectFormat) + } + if datum.Start != nil { + object["start"] = test.NewObjectFromInt(*datum.Start, objectFormat) + } + if datum.End != nil { + object["end"] = test.NewObjectFromInt(*datum.End, objectFormat) + } + + return object +} diff --git a/data/types/upload/upload.go b/data/types/upload/upload.go index 04a3be05ff..282eba64ca 100644 --- a/data/types/upload/upload.go +++ b/data/types/upload/upload.go @@ -75,6 +75,7 @@ type Upload struct { State *string `json:"-" bson:"_state,omitempty"` // TODO: Should this be returned in JSON? I think so. TimeProcessing *string `json:"timeProcessing,omitempty" bson:"timeProcessing,omitempty"` Version *string `json:"version,omitempty" bson:"version,omitempty"` // TODO: Deprecate in favor of Client.Version + LegacyGroupID *string `json:"-" bson:"_groupId,omitempty"` } func NewUpload(parser structure.ObjectParser) *Upload { diff --git a/dexcom/alert_schedule.go b/dexcom/alert_schedule.go index 859b4337e1..24d3d2786c 100644 --- a/dexcom/alert_schedule.go +++ b/dexcom/alert_schedule.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" dataTypesSettingsCgm "github.com/tidepool-org/platform/data/types/settings/cgm" "github.com/tidepool-org/platform/errors" "github.com/tidepool-org/platform/pointer" @@ -22,13 +23,13 @@ const ( AlertScheduleSettingsEndTimeDefault = "00:00" AlertScheduleSettingsEndTimeDefaultAlternate = "0:00" - AlertScheduleSettingsDaySunday = "sunday" - AlertScheduleSettingsDayMonday = "monday" - AlertScheduleSettingsDayTuesday = "tuesday" - AlertScheduleSettingsDayWednesday = "wednesday" - AlertScheduleSettingsDayThursday = "thursday" - AlertScheduleSettingsDayFriday = "friday" - AlertScheduleSettingsDaySaturday = "saturday" + AlertScheduleSettingsDaySunday = dataTypesCommon.DaySunday + AlertScheduleSettingsDayMonday = dataTypesCommon.DayMonday + AlertScheduleSettingsDayTuesday = dataTypesCommon.DayTuesday + AlertScheduleSettingsDayWednesday = dataTypesCommon.DayWednesday + AlertScheduleSettingsDayThursday = dataTypesCommon.DayThursday + AlertScheduleSettingsDayFriday = dataTypesCommon.DayFriday + AlertScheduleSettingsDaySaturday = dataTypesCommon.DaySaturday AlertScheduleSettingsOverrideModeUnknown = "unknown" AlertScheduleSettingsOverrideModeQuiet = "quiet" @@ -455,7 +456,7 @@ func (a *AlertScheduleSettings) Validate(validator structure.Validator) { func (a *AlertScheduleSettings) Normalize(normalizer structure.Normalizer) { if a.DaysOfWeek != nil { - sort.Sort(DaysOfWeekByDay(*a.DaysOfWeek)) + sort.Sort(dataTypesCommon.DaysOfWeekByDayIndex(*a.DaysOfWeek)) } } diff --git a/dexcom/fetch/translate.go b/dexcom/fetch/translate.go index d6361a2c83..476a256245 100644 --- a/dexcom/fetch/translate.go +++ b/dexcom/fetch/translate.go @@ -13,6 +13,7 @@ import ( dataTypesAlert "github.com/tidepool-org/platform/data/types/alert" dataTypesBloodGlucoseContinuous "github.com/tidepool-org/platform/data/types/blood/glucose/continuous" dataTypesBloodGlucoseSelfMonitored "github.com/tidepool-org/platform/data/types/blood/glucose/selfmonitored" + dataTypesCommon "github.com/tidepool-org/platform/data/types/common" dataTypesDeviceCalibration "github.com/tidepool-org/platform/data/types/device/calibration" dataTypesFood "github.com/tidepool-org/platform/data/types/food" dataTypesInsulin "github.com/tidepool-org/platform/data/types/insulin" @@ -228,19 +229,19 @@ func translateAlertScheduleSettingsDaysOfWeekToScheduledAlertDays(daysOfWeek *[] func translateAlertScheduleSettingsDayOfWeekToScheduledAlertDay(dayOfWeek string) string { switch dayOfWeek { case dexcom.AlertScheduleSettingsDaySunday: - return dataTypesSettingsCgm.ScheduledAlertDaysSunday + return dataTypesCommon.DaySunday case dexcom.AlertScheduleSettingsDayMonday: - return dataTypesSettingsCgm.ScheduledAlertDaysMonday + return dataTypesCommon.DayMonday case dexcom.AlertScheduleSettingsDayTuesday: - return dataTypesSettingsCgm.ScheduledAlertDaysTuesday + return dataTypesCommon.DayTuesday case dexcom.AlertScheduleSettingsDayWednesday: - return dataTypesSettingsCgm.ScheduledAlertDaysWednesday + return dataTypesCommon.DayWednesday case dexcom.AlertScheduleSettingsDayThursday: - return dataTypesSettingsCgm.ScheduledAlertDaysThursday + return dataTypesCommon.DayThursday case dexcom.AlertScheduleSettingsDayFriday: - return dataTypesSettingsCgm.ScheduledAlertDaysFriday + return dataTypesCommon.DayFriday case dexcom.AlertScheduleSettingsDaySaturday: - return dataTypesSettingsCgm.ScheduledAlertDaysSaturday + return dataTypesCommon.DaySaturday } return "" } diff --git a/go.mod b/go.mod index 1ada6b1ad3..1b89cfb4b8 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,11 @@ require ( syreclabs.com/go/faker v1.2.3 ) +require ( + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect +) + require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect @@ -84,6 +89,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/r3labs/diff/v3 v3.0.1 github.com/radovskyb/watcher v1.0.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 389228ddfe..f66c282683 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= +github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -169,6 +171,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -192,6 +195,10 @@ github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +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 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= diff --git a/prescription/test/prescription.go b/prescription/test/prescription.go index a3ace93185..f367cdf167 100644 --- a/prescription/test/prescription.go +++ b/prescription/test/prescription.go @@ -4,10 +4,9 @@ import ( "fmt" "time" - dataTypesSettingsPump "github.com/tidepool-org/platform/data/types/settings/pump" - dataBloodGlucoseTest "github.com/tidepool-org/platform/data/blood/glucose/test" "github.com/tidepool-org/platform/data/types/settings/pump" + dataTypesSettingsPump "github.com/tidepool-org/platform/data/types/settings/pump" "github.com/google/uuid" diff --git a/vendor/github.com/r3labs/diff/v3/.gitignore b/vendor/github.com/r3labs/diff/v3/.gitignore new file mode 100644 index 0000000000..e04e61e4f9 --- /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 0000000000..3c00b27c5e --- /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 0000000000..a612ad9813 --- /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 0000000000..f119f8c1e0 --- /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 0000000000..97aee65e79 --- /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 0000000000..b0f3a73802 --- /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 0000000000..f92ff6bdd2 --- /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 0000000000..3c2ba17663 --- /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 0000000000..0e30503368 --- /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 0000000000..7359d17f5f --- /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 0000000000..9494365e87 --- /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 0000000000..3658bf77ca --- /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 0000000000..ef6cde8771 --- /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 0000000000..675ff931f2 --- /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 0000000000..7c9d875144 --- /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 0000000000..3fd281b991 --- /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 0000000000..74182e2fe4 --- /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 0000000000..fb14c57cce --- /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 0000000000..4275e4aeaa --- /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 0000000000..fbe133f1f9 --- /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 0000000000..0acc13ff09 --- /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 0000000000..12e549a6ab --- /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 0000000000..fadbe86e67 --- /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 0000000000..5814c536d7 --- /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 0000000000..6c3f0352e8 --- /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 0000000000..9a703e57a8 --- /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 0000000000..4e2d247f1f --- /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 0000000000..8b7f044ad1 --- /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 0000000000..e2ce06c49f --- /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 0000000000..f6b19d5ba4 --- /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 0000000000..b749d07079 --- /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 0000000000..e9aade7829 --- /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 0000000000..66ad98b9c8 --- /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 0000000000..4fedde6daf --- /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 0000000000..5df40e5d9c --- /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 0000000000..52e0526cc5 --- /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 0000000000..45d6a74186 --- /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 0000000000..c302ed1f33 --- /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 0000000000..db6f7c5472 --- /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 0000000000..e837e08bf1 --- /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 0000000000..d2ff2aea50 --- /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 0000000000..0ef6212e63 --- /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 0000000000..ba4c61be72 --- /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 0000000000..63c311bfae --- /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 0000000000..ca46eadae5 --- /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 0000000000..48cf489fa1 --- /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 0000000000..76e11603d9 --- /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 0000000000..be0316a83d --- /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 0000000000..4db2fa2c71 --- /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 0000000000..e35389cccf --- /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 0000000000..298910d45c --- /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 0000000000..8352c9dcef --- /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 0000000000..44566ec076 --- /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 0000000000..69aca611b2 --- /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 0000000000..192ac47920 --- /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 0000000000..1d49337c35 --- /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 0000000000..7194cd0010 --- /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 0000000000..3fc93fdff8 --- /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 0000000000..0b1b59595a --- /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 0000000000..c0259de565 --- /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 0000000000..21a9bc7f74 --- /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 0000000000..870fe541f0 --- /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 0000000000..f8bc18d911 --- /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 0000000000..5002e6453e --- /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 717a17d095..fbacc10297 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -348,6 +348,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/radovskyb/watcher v1.0.7 ## explicit github.com/radovskyb/watcher @@ -386,6 +389,15 @@ github.com/ugorji/go/codec # github.com/urfave/cli v1.22.15 ## explicit; go 1.11 github.com/urfave/cli +# 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/xdg-go/pbkdf2 v1.0.0 ## explicit; go 1.9 github.com/xdg-go/pbkdf2