forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse_units.go
125 lines (106 loc) · 2.88 KB
/
parse_units.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright 2022 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package topdown
import (
"encoding/json"
"fmt"
"math/big"
"strings"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
// Binary Si unit constants are borrowed from topdown/parse_bytes
const siMilli = 0.001
const (
siK uint64 = 1000
siM = siK * 1000
siG = siM * 1000
siT = siG * 1000
siP = siT * 1000
siE = siP * 1000
)
func parseUnitsError(msg string) error {
return fmt.Errorf("%s: %s", ast.UnitsParse.Name, msg)
}
func errUnitNotRecognized(unit string) error {
return parseUnitsError(fmt.Sprintf("unit %s not recognized", unit))
}
var (
errNoAmount = parseUnitsError("no amount provided")
errNumConv = parseUnitsError("could not parse amount to a number")
errIncludesSpaces = parseUnitsError("spaces not allowed in resource strings")
)
// Accepts both normal SI and binary SI units.
func builtinUnits(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
var x big.Rat
raw, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}
// We remove escaped quotes from strings here to retain parity with units.parse_bytes.
s := string(raw)
s = strings.Replace(s, "\"", "", -1)
if strings.Contains(s, " ") {
return errIncludesSpaces
}
num, unit := extractNumAndUnit(s)
if num == "" {
return errNoAmount
}
// Unlike in units.parse_bytes, we only lowercase after the first letter,
// so that we can distinguish between 'm' and 'M'.
if len(unit) > 1 {
lower := strings.ToLower(unit[1:])
unit = unit[:1] + lower
}
switch unit {
case "m":
x.SetFloat64(siMilli)
case "":
x.SetUint64(none)
case "k", "K":
x.SetUint64(siK)
case "ki", "Ki":
x.SetUint64(ki)
case "M":
x.SetUint64(siM)
case "mi", "Mi":
x.SetUint64(mi)
case "g", "G":
x.SetUint64(siG)
case "gi", "Gi":
x.SetUint64(gi)
case "t", "T":
x.SetUint64(siT)
case "ti", "Ti":
x.SetUint64(ti)
case "p", "P":
x.SetUint64(siP)
case "pi", "Pi":
x.SetUint64(pi)
case "e", "E":
x.SetUint64(siE)
case "ei", "Ei":
x.SetUint64(ei)
default:
return errUnitNotRecognized(unit)
}
numRat, ok := new(big.Rat).SetString(num)
if !ok {
return errNumConv
}
numRat.Mul(numRat, &x)
// Cleaner printout when we have a pure integer value.
if numRat.IsInt() {
return iter(ast.NumberTerm(json.Number(numRat.Num().String())))
}
// When using just big.Float, we had floating-point precision
// issues because quantities like 0.001 are not exactly representable.
// Rationals (such as big.Rat) do not suffer this problem, but are
// more expensive to compute with in general.
return iter(ast.NumberTerm(json.Number(numRat.FloatString(10))))
}
func init() {
RegisterBuiltinFunc(ast.UnitsParse.Name, builtinUnits)
}