From ce9c997584e2c48a3a9fb5d3293b83efaa84f6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Thu, 23 Apr 2020 17:53:57 +0200 Subject: [PATCH] Respect Number field's specified precision --- compose/service/values/sanitizer.go | 66 +++++++++++++++++++++--- compose/service/values/sanitizer_test.go | 51 +++++++++++++++--- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/compose/service/values/sanitizer.go b/compose/service/values/sanitizer.go index c98ba16c4..133195f29 100644 --- a/compose/service/values/sanitizer.go +++ b/compose/service/values/sanitizer.go @@ -1,10 +1,11 @@ package values import ( - "github.com/cortezaproject/corteza-server/compose/types" "strconv" "strings" "time" + + "github.com/cortezaproject/corteza-server/compose/types" ) type ( @@ -88,14 +89,14 @@ func (s sanitizer) Run(m *types.Module, vv types.RecordValueSet) (out types.Reco v = s.sBool(v, f, m) case "datetime": v = s.sDatetime(v, f, m) + case "number": + v = s.sNumber(v, f, m) // Uncomment when they become relevant for sanitization //case "email": // v = s.sEmail(v, f, m) //case "file": // v = s.sFile(v, f, m) - //case "number": - // v = s.sNumber(v, f, m) //case "record": // v = s.sRecord(v, f, m) //case "select": @@ -192,6 +193,60 @@ func (sanitizer) sDatetime(v *types.RecordValue, f *types.ModuleField, m *types. return v } +func (sanitizer) sNumber(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue { + // No point in continuing + if v.Value == "" || f.Options == nil { + return v + } + + // Default to 0 for consistency + if f.Options["precision"] == nil || f.Options["precision"] == "" { + f.Options["precision"] = 0 + } + + // Since Options are not structured, there would appear that there can be a bit of a mess + // when it comes to types,so this is needed. + var prec float64 + unk := f.Options["precision"] + switch i := unk.(type) { + case float64: + prec = i + case int: + prec = float64(i) + case int64: + prec = float64(i) + case string: + pp, err := strconv.ParseFloat(i, 64) + if err != nil { + prec = 0 + break + } + prec = pp + } + + // Clamp between 0 and 6; this was originally done in corteza-js so we keep it here. + if prec < 0 { + prec = 0 + } + if prec > 6 { + prec = 6 + } + + base, err := strconv.ParseFloat(v.Value, 64) + if err != nil { + return v + } + + // 1. Format the value to the desired precision + // 2. In case of fractures, remove trailing 0's + v.Value = strconv.FormatFloat(base, 'f', int(prec), 64) + if strings.Contains(v.Value, ".") { + v.Value = strings.TrimRight(v.Value, "0") + } + + return v +} + // //func (sanitizer) sEmail(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue { // // @todo extract from "name" format @@ -202,11 +257,6 @@ func (sanitizer) sDatetime(v *types.RecordValue, f *types.ModuleField, m *types. // return v //} // -//func (sanitizer) sNumber(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue { -// // @todo cut off the decimals / round up -// // @todo sanitize decimal/thousands dot/comma -// return v -//} // //func (sanitizer) sRecord(v *types.RecordValue, f *types.ModuleField, m *types.Module) *types.RecordValue { // return v diff --git a/compose/service/values/sanitizer_test.go b/compose/service/values/sanitizer_test.go index f53e903ed..38ee0579c 100644 --- a/compose/service/values/sanitizer_test.go +++ b/compose/service/values/sanitizer_test.go @@ -1,18 +1,20 @@ package values import ( - "github.com/cortezaproject/corteza-server/compose/types" "reflect" "testing" + + "github.com/cortezaproject/corteza-server/compose/types" ) func Test_sanitizer_Run(t *testing.T) { tests := []struct { - name string - kind string - input string - output string - outref uint64 + name string + kind string + options map[string]interface{} + input string + output string + outref uint64 }{ { name: "numbers should be trimmed", @@ -88,11 +90,46 @@ func Test_sanitizer_Run(t *testing.T) { input: "2020-03-11T11:20:08.471Z", output: "2020-03-11T11:20:08Z", }, + { + name: "number precision", + kind: "Number", + options: map[string]interface{}{"precision": 3}, + input: "42.44455", + output: "42.445", + }, + { + name: "number precision; bigger precision then provided fracture", + kind: "Number", + options: map[string]interface{}{"precision": 3}, + input: "42.4", + output: "42.4", + }, + { + name: "number precision; default to 0", + kind: "Number", + options: map[string]interface{}{}, + input: "42.4", + output: "42", + }, + { + name: "number precision; clamped between [0, 6]", + kind: "Number", + options: map[string]interface{}{"precision": 10}, + input: "42.5555555555", + output: "42.555556", + }, + { + name: "number precision; clamped between [0, 6]", + kind: "Number", + options: map[string]interface{}{"precision": -1}, + input: "42.4", + output: "42", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := sanitizer{} - m := &types.Module{Fields: types.ModuleFieldSet{&types.ModuleField{Name: "testField", Kind: tt.kind}}} + m := &types.Module{Fields: types.ModuleFieldSet{&types.ModuleField{Name: "testField", Kind: tt.kind, Options: tt.options}}} v := types.RecordValueSet{&types.RecordValue{Name: "testField", Value: tt.input}} o := types.RecordValueSet{&types.RecordValue{Name: "testField", Value: tt.output, Ref: tt.outref}}