diff --git a/mqlc/labels.go b/mqlc/labels.go index 9c747e086b..0050d14321 100644 --- a/mqlc/labels.go +++ b/mqlc/labels.go @@ -5,6 +5,7 @@ package mqlc import ( "errors" + "regexp" "strconv" "go.mondoo.com/cnquery/llx" @@ -73,12 +74,20 @@ func createLabel(res *llx.CodeBundle, ref uint64, schema llx.Schema) (string, er case types.Int: label = "[" + strconv.FormatInt(idx.(int64), 10) + "]" case types.String: - label = "[" + idx.(string) + "]" + if chunk.Function.Type == string(types.Dict) && isAccessor(idx.(string)) { + label = idx.(string) + } else { + label = "[" + idx.(string) + "]" + } default: panic("cannot label array index of type " + arg.Type.Label()) } if parentLabel != "" { - label = parentLabel + label + if label != "" && label[0] == '[' { + label = parentLabel + label + } else { + label = parentLabel + "." + label + } } case "{}", "${}": label = parentLabel @@ -101,6 +110,12 @@ func createLabel(res *llx.CodeBundle, ref uint64, schema llx.Schema) (string, er return stripCtlAndExtFromUnicode(label), nil } +var reAccessor = regexp.MustCompile(`^[\p{L}\d_]+$`) + +func isAccessor(s string) bool { + return reAccessor.MatchString(s) +} + // Unicode normalization and filtering, see http://blog.golang.org/normalization and // http://godoc.org/golang.org/x/text/unicode/norm for more details. func stripCtlAndExtFromUnicode(str string) string { diff --git a/mqlc/mqlc.go b/mqlc/mqlc.go index 9b6cb00074..5cdeca522e 100644 --- a/mqlc/mqlc.go +++ b/mqlc/mqlc.go @@ -1266,6 +1266,22 @@ func (c *compiler) compileIdentifier(id string, callBinding *variable, calls []* return restCalls, typ, err } + // Support easy accessors for dicts and maps, e.g: + // json.params { A.B.C } => json.params { _["A"]["B"]["C"] } + if callBinding != nil && callBinding.typ == types.Dict { + c.addChunk(&llx.Chunk{ + Call: llx.Chunk_FUNCTION, + Id: "[]", + Function: &llx.Function{ + Type: string(callBinding.typ), + Binding: callBinding.ref, + Args: []*llx.Primitive{llx.StringPrimitive(id)}, + }, + }) + c.standalone = false + return restCalls, callBinding.typ, err + } + // suggestions if callBinding == nil { addResourceSuggestions(c.Schema, id, c.Result) @@ -1510,11 +1526,26 @@ func (c *compiler) compileOperand(operand *parser.Operand) (*llx.Primitive, erro return nil, err } if !found { - addFieldSuggestions(availableFields(c, typ), id, c.Result) - return nil, errors.New("cannot find field '" + id + "' in " + typ.Label()) + if typ != types.Dict || !reAccessor.MatchString(id) { + addFieldSuggestions(availableFields(c, typ), id, c.Result) + return nil, errors.New("cannot find field '" + id + "' in " + typ.Label()) + } + + // Support easy accessors for dicts and maps, e.g: + // json.params.A.B.C => json.params["A"]["B"]["C"] + c.addChunk(&llx.Chunk{ + Call: llx.Chunk_FUNCTION, + Id: "[]", + Function: &llx.Function{ + Type: string(typ), + Binding: ref, + Args: []*llx.Primitive{llx.StringPrimitive(id)}, + }, + }) + } else { + typ = resType } - typ = resType if call != nil && len(calls) > 0 { calls = calls[1:] } diff --git a/mqlc/mqlc_test.go b/mqlc/mqlc_test.go index 3c1ded231f..da03b2fb7b 100644 --- a/mqlc/mqlc_test.go +++ b/mqlc/mqlc_test.go @@ -489,6 +489,40 @@ func TestCompiler_Props(t *testing.T) { }) } +func TestCompiler_Dict(t *testing.T) { + compileProps(t, "props.d.A.B", map[string]*llx.Primitive{ + "d": {Type: string(types.Dict)}, + }, func(res *llx.CodeBundle) { + assertProperty(t, "d", types.Dict, res.CodeV2.Blocks[0].Chunks[0]) + assert.Equal(t, []uint64{(1 << 32) | 3}, res.CodeV2.Entrypoints()) + assertFunction(t, "[]", &llx.Function{ + Type: string(types.Dict), + Binding: (1 << 32) | 1, + Args: []*llx.Primitive{llx.StringPrimitive("A")}, + }, res.CodeV2.Blocks[0].Chunks[1]) + assertFunction(t, "[]", &llx.Function{ + Type: string(types.Dict), + Binding: (1 << 32) | 2, + Args: []*llx.Primitive{llx.StringPrimitive("B")}, + }, res.CodeV2.Blocks[0].Chunks[2]) + assert.Equal(t, map[string]string{"d": string(types.Dict)}, res.Props) + }) + + compileProps(t, "props.d.A-1", map[string]*llx.Primitive{ + "d": {Type: string(types.Dict)}, + }, func(res *llx.CodeBundle) { + assertProperty(t, "d", types.Dict, res.CodeV2.Blocks[0].Chunks[0]) + assert.Equal(t, []uint64{(1 << 32) | 2, (1 << 32) | 3}, res.CodeV2.Entrypoints()) + assertFunction(t, "[]", &llx.Function{ + Type: string(types.Dict), + Binding: (1 << 32) | 1, + Args: []*llx.Primitive{llx.StringPrimitive("A")}, + }, res.CodeV2.Blocks[0].Chunks[1]) + assertPrimitive(t, llx.IntPrimitive(-1), res.CodeV2.Blocks[0].Chunks[2]) + assert.Equal(t, map[string]string{"d": string(types.Dict)}, res.Props) + }) +} + func TestCompiler_If(t *testing.T) { compileT(t, "if ( true ) { return 1 } else if ( false ) { return 2 } else { return 3 }", func(res *llx.CodeBundle) { assertFunction(t, "if", &llx.Function{ diff --git a/providers/os/resources/parse.go b/providers/os/resources/parse.go index 0e3b4fb9ec..5d8871d114 100644 --- a/providers/os/resources/parse.go +++ b/providers/os/resources/parse.go @@ -114,7 +114,7 @@ func (s *mqlParseJson) id() (string, error) { } func (s *mqlParseJson) content(file *mqlFile) (string, error) { - c := file.Content + c := file.GetContent() return c.Data, c.Error }