Improve number sanitization (precision)
This commit is contained in:
parent
5290441cae
commit
df6909286a
@ -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")
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
33
compose/types/module_field_options_test.go
Normal file
33
compose/types/module_field_options_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user