forked from open-policy-agent/opa
/
parse_bytes.go
155 lines (125 loc) · 3.2 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
144
145
146
147
148
149
150
151
152
153
154
155
// 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"
"strconv"
"strings"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
type multiplier int
const (
none multiplier = 1
kb = 1000
ki = 1024
mb = kb * 1000
mi = ki * 1024
gb = mb * 1000
gi = mi * 1024
tb = gb * 1000
ti = gi * 1024
)
// The rune values for 0..9 as well as the period symbol (for parsing floats)
var numRunes = []rune("0123456789.")
func parseNumBytesError(msg string) error {
return fmt.Errorf("%s error: %s", ast.UnitsParseBytes.Name, msg)
}
func errUnitNotRecognized(unit string) error {
return parseNumBytesError(fmt.Sprintf("byte unit %s not recognized", unit))
}
var (
errNoAmount = parseNumBytesError("no byte amount provided")
errIntConv = parseNumBytesError("could not parse byte amount to integer")
errIncludesSpaces = parseNumBytesError("spaces not allowed in resource strings")
)
func builtinNumBytes(a ast.Value) (ast.Value, error) {
var m multiplier
raw, err := builtins.StringOperand(a, 1)
if err != nil {
return nil, err
}
s := formatString(raw)
if strings.Contains(s, " ") {
return nil, errIncludesSpaces
}
numStr, unitStr := extractNumAndUnit(s)
if numStr == "" {
return nil, errNoAmount
}
switch unitStr {
case "":
m = none
case "kb":
m = kb
case "kib":
m = ki
case "mb":
m = mb
case "mib":
m = mi
case "gb":
m = gb
case "gib":
m = gi
case "tb":
m = tb
case "tib":
m = ti
default:
return nil, errUnitNotRecognized(unitStr)
}
num, err := strconv.Atoi(numStr)
if err != nil {
return nil, errIntConv
}
total := num * int(m)
return builtins.IntToNumber(big.NewInt(int64(total))), nil
}
// Makes the string lower case and removes spaces and 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) (isNum bool) {
for _, nr := range numRunes {
if nr == r {
return true
}
}
return false
}
// Returns the index of the first rune that's not a number (or 0 if there are only numbers)
getFirstNonNumIdx := func(s string) int {
for idx, r := range s {
if !isNum(r) {
return idx
}
}
return 0
}
firstRuneIsNum := func(s string) bool {
return isNum(rune(s[0]))
}
firstNonNumIdx := getFirstNonNumIdx(s)
// The string contains only a number
numOnly := firstNonNumIdx == 0 && firstRuneIsNum(s)
// The string contains only a unit
unitOnly := firstNonNumIdx == 0 && !firstRuneIsNum(s)
if numOnly {
return s, ""
} else if unitOnly {
return "", s
} else {
return s[0:firstNonNumIdx], s[firstNonNumIdx:]
}
}
func init() {
RegisterFunctionalBuiltin1(ast.UnitsParseBytes.Name, builtinNumBytes)
}