forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse_bytes.go
143 lines (121 loc) · 3 KB
/
parse_bytes.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2016 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 (
"fmt"
"math/big"
"strings"
"unicode"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
const (
none uint64 = 1 << (10 * iota)
ki
mi
gi
ti
pi
ei
kb uint64 = 1000
mb = kb * 1000
gb = mb * 1000
tb = gb * 1000
pb = tb * 1000
eb = pb * 1000
)
func parseNumBytesError(msg string) error {
return fmt.Errorf("%s: %s", ast.UnitsParseBytes.Name, msg)
}
func errBytesUnitNotRecognized(unit string) error {
return parseNumBytesError(fmt.Sprintf("byte unit %s not recognized", unit))
}
var (
errBytesValueNoAmount = parseNumBytesError("no byte amount provided")
errBytesValueNumConv = parseNumBytesError("could not parse byte amount to a number")
errBytesValueIncludesSpaces = parseNumBytesError("spaces not allowed in resource strings")
)
func builtinNumBytes(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
var m big.Float
raw, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}
s := formatString(raw)
if strings.Contains(s, " ") {
return errBytesValueIncludesSpaces
}
num, unit := extractNumAndUnit(s)
if num == "" {
return errBytesValueNoAmount
}
switch unit {
case "":
m.SetUint64(none)
case "kb", "k":
m.SetUint64(kb)
case "kib", "ki":
m.SetUint64(ki)
case "mb", "m":
m.SetUint64(mb)
case "mib", "mi":
m.SetUint64(mi)
case "gb", "g":
m.SetUint64(gb)
case "gib", "gi":
m.SetUint64(gi)
case "tb", "t":
m.SetUint64(tb)
case "tib", "ti":
m.SetUint64(ti)
case "pb", "p":
m.SetUint64(pb)
case "pib", "pi":
m.SetUint64(pi)
case "eb", "e":
m.SetUint64(eb)
case "eib", "ei":
m.SetUint64(ei)
default:
return errBytesUnitNotRecognized(unit)
}
numFloat, ok := new(big.Float).SetString(num)
if !ok {
return errBytesValueNumConv
}
var total big.Int
numFloat.Mul(numFloat, &m).Int(&total)
return iter(ast.NewTerm(builtins.IntToNumber(&total)))
}
// Makes the string lower case and removes quotation marks
func formatString(s ast.String) string {
str := string(s)
lower := strings.ToLower(str)
return strings.Replace(lower, "\"", "", -1)
}
// Splits the string into a number string à la "10" or "10.2" and a unit
// string à la "gb" or "MiB" or "foo". Either can be an empty string
// (error handling is provided elsewhere).
func extractNumAndUnit(s string) (string, string) {
isNum := func(r rune) bool {
return unicode.IsDigit(r) || r == '.'
}
firstNonNumIdx := -1
for idx, r := range s {
if !isNum(r) {
firstNonNumIdx = idx
break
}
}
if firstNonNumIdx == -1 { // only digits and '.'
return s, ""
}
if firstNonNumIdx == 0 { // only units (starts with non-digit)
return "", s
}
return s[0:firstNonNumIdx], s[firstNonNumIdx:]
}
func init() {
RegisterBuiltinFunc(ast.UnitsParseBytes.Name, builtinNumBytes)
}