// 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 ( "errors" "fmt" "strings" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/topdown/builtins" ) func builtinFormatInt(a, b ast.Value) (ast.Value, error) { input, err := builtins.NumberOperand(a, 1) if err != nil { return nil, err } base, err := builtins.NumberOperand(b, 2) if err != nil { return nil, err } var format string switch base { case ast.Number("2"): format = "%b" case ast.Number("8"): format = "%o" case ast.Number("10"): format = "%d" case ast.Number("16"): format = "%x" default: return nil, builtins.NewOperandEnumErr(2, "2", "8", "10", "16") } f := builtins.NumberToFloat(input) i, _ := f.Int(nil) return ast.String(fmt.Sprintf(format, i)), nil } func builtinConcat(a, b ast.Value) (ast.Value, error) { join, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } strs := []string{} switch b := b.(type) { case ast.Array: for i := range b { s, ok := b[i].Value.(ast.String) if !ok { return nil, builtins.NewOperandElementErr(2, b, b[i].Value, "string") } strs = append(strs, string(s)) } case ast.Set: err := b.Iter(func(x *ast.Term) error { s, ok := x.Value.(ast.String) if !ok { return builtins.NewOperandElementErr(2, b, x.Value, "string") } strs = append(strs, string(s)) return nil }) if err != nil { return nil, err } default: return nil, builtins.NewOperandTypeErr(2, b, "set", "array") } return ast.String(strings.Join(strs, string(join))), nil } func builtinIndexOf(a, b ast.Value) (ast.Value, error) { base, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } search, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } index := strings.Index(string(base), string(search)) return ast.IntNumberTerm(index).Value, nil } func builtinSubstring(a, b, c ast.Value) (ast.Value, error) { base, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } startIndex, err := builtins.IntOperand(b, 2) if err != nil { return nil, err } else if startIndex >= len(base) { return ast.String(""), nil } else if startIndex < 0 { return nil, fmt.Errorf("negative offset") } length, err := builtins.IntOperand(c, 3) if err != nil { return nil, err } var s ast.String if length < 0 { s = ast.String(base[startIndex:]) } else { upto := startIndex + length if len(base) < upto { upto = len(base) } s = ast.String(base[startIndex:upto]) } return s, nil } func builtinContains(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } substr, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.Boolean(strings.Contains(string(s), string(substr))), nil } func builtinStartsWith(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } prefix, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.Boolean(strings.HasPrefix(string(s), string(prefix))), nil } func builtinEndsWith(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } suffix, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.Boolean(strings.HasSuffix(string(s), string(suffix))), nil } func builtinLower(a ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } return ast.String(strings.ToLower(string(s))), nil } func builtinUpper(a ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } return ast.String(strings.ToUpper(string(s))), nil } func builtinSplit(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } d, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } elems := strings.Split(string(s), string(d)) arr := make(ast.Array, len(elems)) for i := range arr { arr[i] = ast.StringTerm(elems[i]) } return arr, nil } func builtinReplace(a, b, c ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } old, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } new, err := builtins.StringOperand(c, 3) if err != nil { return nil, err } return ast.String(strings.Replace(string(s), string(old), string(new), -1)), nil } func builtinReplaceN(a, b ast.Value) (ast.Value, error) { asJSON, err := ast.JSON(a) if err != nil { return nil, err } oldnewObj, ok := asJSON.(map[string]interface{}) if !ok { return nil, builtins.NewOperandTypeErr(1, a, "object") } s, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } var oldnewArr []string for k, v := range oldnewObj { strVal, ok := v.(string) if !ok { return nil, errors.New("non-string value found in pattern object") } oldnewArr = append(oldnewArr, k, strVal) } r := strings.NewReplacer(oldnewArr...) replaced := r.Replace(string(s)) return ast.String(replaced), nil } func builtinTrim(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } c, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.String(strings.Trim(string(s), string(c))), nil } func builtinTrimLeft(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } c, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.String(strings.TrimLeft(string(s), string(c))), nil } func builtinTrimPrefix(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } pre, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.String(strings.TrimPrefix(string(s), string(pre))), nil } func builtinTrimRight(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } c, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.String(strings.TrimRight(string(s), string(c))), nil } func builtinTrimSuffix(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } suf, err := builtins.StringOperand(b, 2) if err != nil { return nil, err } return ast.String(strings.TrimSuffix(string(s), string(suf))), nil } func builtinTrimSpace(a ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } return ast.String(strings.TrimSpace(string(s))), nil } func builtinSprintf(a, b ast.Value) (ast.Value, error) { s, err := builtins.StringOperand(a, 1) if err != nil { return nil, err } astArr, ok := b.(ast.Array) if !ok { return nil, builtins.NewOperandTypeErr(2, b, "array") } args := make([]interface{}, len(astArr)) for i := range astArr { switch v := astArr[i].Value.(type) { case ast.Number: if n, ok := v.Int(); ok { args[i] = n } else if f, ok := v.Float64(); ok { args[i] = f } else { args[i] = v.String() } case ast.String: args[i] = string(v) default: args[i] = astArr[i].String() } } return ast.String(fmt.Sprintf(string(s), args...)), nil } func init() { RegisterFunctionalBuiltin2(ast.FormatInt.Name, builtinFormatInt) RegisterFunctionalBuiltin2(ast.Concat.Name, builtinConcat) RegisterFunctionalBuiltin2(ast.IndexOf.Name, builtinIndexOf) RegisterFunctionalBuiltin3(ast.Substring.Name, builtinSubstring) RegisterFunctionalBuiltin2(ast.Contains.Name, builtinContains) RegisterFunctionalBuiltin2(ast.StartsWith.Name, builtinStartsWith) RegisterFunctionalBuiltin2(ast.EndsWith.Name, builtinEndsWith) RegisterFunctionalBuiltin1(ast.Upper.Name, builtinUpper) RegisterFunctionalBuiltin1(ast.Lower.Name, builtinLower) RegisterFunctionalBuiltin2(ast.Split.Name, builtinSplit) RegisterFunctionalBuiltin3(ast.Replace.Name, builtinReplace) RegisterFunctionalBuiltin2(ast.ReplaceN.Name, builtinReplaceN) RegisterFunctionalBuiltin2(ast.Trim.Name, builtinTrim) RegisterFunctionalBuiltin2(ast.TrimLeft.Name, builtinTrimLeft) RegisterFunctionalBuiltin2(ast.TrimPrefix.Name, builtinTrimPrefix) RegisterFunctionalBuiltin2(ast.TrimRight.Name, builtinTrimRight) RegisterFunctionalBuiltin2(ast.TrimSuffix.Name, builtinTrimSuffix) RegisterFunctionalBuiltin1(ast.TrimSpace.Name, builtinTrimSpace) RegisterFunctionalBuiltin2(ast.Sprintf.Name, builtinSprintf) }