3
0

Improve number sanitization (precision)

This commit is contained in:
Denis Arh 2020-06-24 11:25:09 +02:00
parent 5290441cae
commit df6909286a
5 changed files with 95 additions and 47 deletions

View File

@ -193,53 +193,30 @@ func (sanitizer) sDatetime(v *types.RecordValue, f *types.ModuleField, m *types.
return v
}
// sNumber sanitizes
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 {
v.Value = "0"
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)
// calculate percision
var p = 0
if f.Options != nil {
p = int(f.Options.Int64(fieldOpt_Number_precision))
if p < fieldOpt_Number_precision_min {
p = fieldOpt_Number_precision_min
} else if p > fieldOpt_Number_precision_max {
p = fieldOpt_Number_precision_max
}
}
// Format the value to the desired precision
v.Value = strconv.FormatFloat(base, 'f', p, 64)
// In case of fractures, remove trailing 0's
if strings.Contains(v.Value, ".") {
v.Value = strings.TrimRight(v.Value, "0")
}

View File

@ -90,6 +90,24 @@ func Test_sanitizer_Run(t *testing.T) {
input: "2020-03-11T11:20:08.471Z",
output: "2020-03-11T11:20:08Z",
},
{
name: "number space trim",
kind: "Number",
input: " 42 ",
output: "42",
},
{
name: "number negative",
kind: "Number",
input: "-42",
output: "-42",
},
{
name: "number positive",
kind: "Number",
input: "+42",
output: "42",
},
{
name: "number precision",
kind: "Number",

View File

@ -18,7 +18,9 @@ const (
fieldOpt_Datetime_onlyFutureValues = "onlyFutureValues"
fieldOpt_Datetime_onlyPastValues = "onlyPastValues"
fieldOpt_Number_precision = "precision"
fieldOpt_Number_precision = "precision"
fieldOpt_Number_precision_min = 0
fieldOpt_Number_precision_max = 6
fieldOpt_Url_onlySecure = "onlySecure"
)

View File

@ -3,7 +3,9 @@ package types
import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"strconv"
)
type (
@ -52,12 +54,28 @@ func (opt ModuleFieldOptions) Int64(key string) int64 {
}
func (opt ModuleFieldOptions) Int64Def(key string, def int64) int64 {
if _, has := opt[key]; has {
if n, ok := opt[key].(int64); ok {
return n
}
}
if val, has := opt[key]; has {
switch conv := val.(type) {
case int:
return int64(conv)
case int64:
return conv
default:
// to avoid covering every possible type, just convert value into string
strVal := fmt.Sprintf("%v", val)
fmt.Printf("\n[%v] key: %s => (%T) %s\n\n", opt, key, val, strVal)
if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
return intVal
}
if floatVal, err := strconv.ParseFloat(strVal, 64); err == nil {
return int64(floatVal)
}
}
}
return def
}

View File

@ -0,0 +1,33 @@
package types
import (
"testing"
)
func TestModuleFieldOptions_Int64Def(t *testing.T) {
tests := []struct {
name string
opt ModuleFieldOptions
key string
def int64
want int64
}{
// TODO: Add test cases.
{"unexisting", ModuleFieldOptions{}, "k", 42, 42},
{"nil", ModuleFieldOptions{"k": nil}, "k", 42, 42},
{"bool", ModuleFieldOptions{"k": true}, "k", 42, 42},
{"int", ModuleFieldOptions{"k": 1}, "k", 42, 1},
{"float", ModuleFieldOptions{"k": 1.00000000001}, "k", 42, 1},
{"stringed-int", ModuleFieldOptions{"k": "1"}, "k", 42, 1},
{"stringed-float-1", ModuleFieldOptions{"k": "1.0"}, "k", 42, 1},
{"stringed-float-2", ModuleFieldOptions{"k": "1.01"}, "k", 42, 1},
{"stringed-float-3", ModuleFieldOptions{"k": "1.00000000001"}, "k", 42, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.opt.Int64Def(tt.key, tt.def); got != tt.want {
t.Errorf("Int64Def() = %v, want %v", got, tt.want)
}
})
}
}