go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/lr/go.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package lr
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"go.mondoo.com/cnquery/types"
    16  	"go.mondoo.com/cnquery/utils/multierr"
    17  )
    18  
    19  // Go produced go code for the LR file
    20  func Go(packageName string, ast *LR, collector *Collector) (string, error) {
    21  	o := goBuilder{
    22  		collector:    collector,
    23  		ast:          ast,
    24  		name:         packageName,
    25  		providerName: filepath.Base(ast.Options["provider"]),
    26  		packsInUse:   map[string]struct{}{},
    27  	}
    28  
    29  	// should generally not happen and is primarily used for logging
    30  	if o.providerName == "" {
    31  		o.providerName = "provider"
    32  	}
    33  
    34  	o.goCreateResource(ast.Resources)
    35  	o.goGetData(ast.Resources)
    36  	o.goSetData(ast.Resources)
    37  
    38  	for i := range ast.Resources {
    39  		o.goResource(ast.Resources[i])
    40  	}
    41  
    42  	imports := ""
    43  	for packName := range o.packsInUse {
    44  		importPath, ok := ast.packPaths[packName]
    45  		if !ok {
    46  			return "", errors.New("cannot find import path for pack: " + packName)
    47  		}
    48  
    49  		imports += "\n\t" + strconv.Quote(importPath)
    50  	}
    51  
    52  	coreImports := ""
    53  	if hasTimeImports(ast, &o) {
    54  		coreImports = "\n\t\"time\""
    55  	}
    56  
    57  	header := fmt.Sprintf(goHeader, coreImports, imports)
    58  	return header + o.data, o.errors.Deduplicate()
    59  }
    60  
    61  func hasTimeImports(ast *LR, b *goBuilder) bool {
    62  	timeType := primitiveTypes["time"]
    63  	for i := range ast.Resources {
    64  		r := ast.Resources[i]
    65  		for j := range r.Body.Fields {
    66  			field := r.Body.Fields[j]
    67  			if field.BasicField != nil && field.BasicField.Type.goType(b) == timeType {
    68  				return true
    69  			}
    70  		}
    71  	}
    72  	return false
    73  }
    74  
    75  var reservedKeywords = map[string]struct{}{}
    76  
    77  func init() {
    78  	// Reserved keywords up to go 1.20
    79  	words := `
    80  	break     default      func    interface  select
    81  	case      defer        go      map        struct
    82  	chan      else         goto    package    switch
    83  	const     fallthrough  if      range      type
    84  	continue  for          import  return     var
    85  	`
    86  	re := regexp.MustCompile(`\S+`)
    87  	terms := re.FindAllString(words, -1)
    88  	for _, term := range terms {
    89  		reservedKeywords[term] = struct{}{}
    90  	}
    91  }
    92  
    93  func fieldCall(f string) string {
    94  	if _, ok := reservedKeywords[f]; ok {
    95  		return "compute_" + f
    96  	}
    97  	return f
    98  }
    99  
   100  type goBuilder struct {
   101  	data         string
   102  	collector    *Collector
   103  	ast          *LR
   104  	errors       multierr.Errors
   105  	name         string
   106  	providerName string
   107  	packsInUse   map[string]struct{}
   108  }
   109  
   110  const goHeader = `// Copyright (c) Mondoo, Inc.
   111  // SPDX-License-Identifier: BUSL-1.1
   112  
   113  // Code generated by resources. DO NOT EDIT.
   114  
   115  package resources
   116  
   117  import (
   118  	"errors"%s
   119  
   120  	"go.mondoo.com/cnquery/llx"
   121  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
   122  	"go.mondoo.com/cnquery/types"%s
   123  )
   124  `
   125  
   126  func (b *goBuilder) goCreateResource(r []*Resource) {
   127  	newCmds := make([]string, len(r))
   128  	for i := range r {
   129  		resource := r[i]
   130  		iName := resource.interfaceName(b)
   131  
   132  		var parseArgs string
   133  		if b.collector.HasInit(iName) {
   134  			parseArgs = "Init: init" + iName + ","
   135  		} else {
   136  			parseArgs = "// to override args, implement: init" + iName + "(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error)"
   137  		}
   138  
   139  		newCmds[i] = fmt.Sprintf("\"%s\": {\n\t\t\t%s\n\t\t\tCreate: create%s,\n\t\t},", resource.ID, parseArgs, iName)
   140  	}
   141  
   142  	b.data += `
   143  var resourceFactories map[string]plugin.ResourceFactory
   144  
   145  func init() {
   146  	resourceFactories = map[string]plugin.ResourceFactory {
   147  		` + strings.Join(newCmds, "\n\t\t") + `
   148  	}
   149  }
   150  
   151  // NewResource is used by the runtime of this plugin to create new resources.
   152  // Its arguments may be provided by users. This function is generally not
   153  // used by initializing resources from recordings or from lists.
   154  func NewResource(runtime *plugin.Runtime, name string, args map[string]*llx.RawData) (plugin.Resource, error) {
   155  	f, ok := resourceFactories[name]
   156  	if !ok {
   157  		return nil, errors.New("cannot find resource " + name + " in this provider")
   158  	}
   159  
   160  	if f.Init != nil {
   161  		cargs, res, err := f.Init(runtime, args)
   162  		if err != nil {
   163  			return res, err
   164  		}
   165  
   166  		if res != nil {
   167  			id := name+"\x00"+res.MqlID()
   168  			if x, ok := runtime.Resources.Get(id); ok {
   169  				return x, nil
   170  			}
   171  			runtime.Resources.Set(id, res)
   172  			return res, nil
   173  		}
   174  
   175  		args = cargs
   176  	}
   177  
   178  	res, err := f.Create(runtime, args)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	id := name+"\x00"+res.MqlID()
   184  	if x, ok := runtime.Resources.Get(id); ok {
   185  		return x, nil
   186  	}
   187  
   188  	runtime.Resources.Set(id, res)
   189  	return res, nil
   190  }
   191  
   192  // CreateResource is used by the runtime of this plugin to create resources.
   193  // Its arguments must be complete and pre-processed. This method is used
   194  // for initializing resources from recordings or from lists.
   195  func CreateResource(runtime *plugin.Runtime, name string, args map[string]*llx.RawData) (plugin.Resource, error) {
   196  	f, ok := resourceFactories[name]
   197  	if !ok {
   198  		return nil, errors.New("cannot find resource " + name + " in this provider")
   199  	}
   200  
   201  	res, err := f.Create(runtime, args)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	id := name+"\x00"+res.MqlID()
   207  	if x, ok := runtime.Resources.Get(id); ok {
   208  		return x, nil
   209  	}
   210  
   211  	runtime.Resources.Set(id, res)
   212  	return res, nil
   213  }
   214  `
   215  }
   216  
   217  func (b *goBuilder) goGetData(r []*Resource) {
   218  	fields := []string{}
   219  	for i := range r {
   220  		resource := r[i]
   221  		for j := range resource.Body.Fields {
   222  			field := resource.Body.Fields[j]
   223  			if field.Init != nil {
   224  				continue
   225  			}
   226  
   227  			x := fmt.Sprintf(`"%s.%s": func(r plugin.Resource) *plugin.DataRes {
   228  		return (r.(*%s).Get%s()).ToDataRes(%s)
   229  	},`,
   230  				resource.ID, field.BasicField.ID,
   231  				resource.structName(b), field.BasicField.methodname(),
   232  				field.BasicField.Type.mondooType(b),
   233  			)
   234  			fields = append(fields, x)
   235  		}
   236  	}
   237  
   238  	b.data += `
   239  var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{
   240  	` + strings.Join(fields, "\n\t") + `
   241  }
   242  
   243  func GetData(resource plugin.Resource, field string, args map[string]*llx.RawData) *plugin.DataRes {
   244  	f, ok := getDataFields[resource.MqlName()+"."+field]
   245  	if !ok {
   246  		return &plugin.DataRes{Error: "cannot find '" + field + "' in resource '" + resource.MqlName() + "'"}
   247  	}
   248  
   249  	return f(resource)
   250  }
   251  `
   252  }
   253  
   254  func (b *goBuilder) goSetData(r []*Resource) {
   255  	fields := []string{}
   256  	for i := range r {
   257  		resource := r[i]
   258  
   259  		x := fmt.Sprintf(`"%s.%s": func(r plugin.Resource, v *llx.RawData) (ok bool) {
   260  			r.(*%s).__id, ok = v.Value.(string)
   261  			return
   262  		},`,
   263  			resource.ID, "__id",
   264  			resource.structName(b),
   265  		)
   266  		fields = append(fields, x)
   267  
   268  		for j := range resource.Body.Fields {
   269  			field := resource.Body.Fields[j]
   270  			if field.Init != nil {
   271  				continue
   272  			}
   273  
   274  			x := fmt.Sprintf(`"%s.%s": func(r plugin.Resource, v *llx.RawData) (ok bool) {
   275  		r.(*%s).%s, ok = plugin.RawToTValue[%s](v.Value, v.Error)
   276  		return
   277  	},`,
   278  				resource.ID, field.BasicField.ID,
   279  				resource.structName(b), field.BasicField.methodname(),
   280  				field.BasicField.Type.goType(b),
   281  			)
   282  			fields = append(fields, x)
   283  		}
   284  	}
   285  
   286  	b.data += fmt.Sprintf(`
   287  var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool {
   288  	%s
   289  }
   290  
   291  func SetData(resource plugin.Resource, field string, val *llx.RawData) error {
   292  	f, ok := setDataFields[resource.MqlName() + "." + field]
   293  	if !ok {
   294  		return errors.New("[%s] cannot set '"+field+"' in resource '"+resource.MqlName()+"', field not found")
   295  	}
   296  
   297  	if ok := f(resource, val); !ok {
   298  		return errors.New("[%s] cannot set '"+field+"' in resource '"+resource.MqlName()+"', type does not match")
   299  	}
   300  	return nil
   301  }
   302  
   303  func SetAllData(resource plugin.Resource, args map[string]*llx.RawData) error {
   304  	var err error
   305  	for k, v := range args {
   306  		if err = SetData(resource, k, v); err != nil {
   307  			return err
   308  		}
   309  	}
   310  	return nil
   311  }
   312  `,
   313  		strings.Join(fields, "\n\t"),
   314  		b.providerName, b.providerName,
   315  	)
   316  }
   317  
   318  func (b *goBuilder) goResource(r *Resource) {
   319  	b.goStruct(r)
   320  	b.goFactory(r)
   321  	b.goFields(r)
   322  }
   323  
   324  func (b *goBuilder) goStruct(r *Resource) {
   325  	internalStruct := r.structName(b) + "Internal"
   326  	if !b.collector.HasStruct(internalStruct) {
   327  		internalStruct = "// optional: if you define " + internalStruct + " it will be used here"
   328  	}
   329  
   330  	fields := []string{}
   331  	for i := range r.Body.Fields {
   332  		field := r.Body.Fields[i]
   333  		if field.Init != nil {
   334  			continue
   335  		}
   336  		fields = append(fields, field.BasicField.goName()+" plugin.TValue["+field.BasicField.Type.goType(b)+"]")
   337  	}
   338  
   339  	sFields := strings.Join(fields, "\n\t")
   340  	if len(fields) != 0 {
   341  		sFields = "\n\t" + sFields
   342  	}
   343  
   344  	b.data += fmt.Sprintf(`
   345  // %s for the %s resource
   346  type %s struct {
   347  	MqlRuntime *plugin.Runtime
   348  	__id string
   349  	%s%s
   350  }
   351  `,
   352  		r.structName(b), r.ID, r.structName(b),
   353  		internalStruct,
   354  		sFields,
   355  	)
   356  }
   357  
   358  func (b *goBuilder) goFactory(r *Resource) {
   359  	createName := "create" + r.interfaceName(b)
   360  	structName := r.structName(b)
   361  
   362  	var idCode string
   363  	if b.collector.HasID(structName) {
   364  		idCode = `if res.__id == "" {
   365  	res.__id, err = res.id()
   366  		if err != nil {
   367  			return nil, err
   368  		}
   369  	}`
   370  	} else {
   371  		idCode = `// to override __id implement: id() (string, error)`
   372  	}
   373  
   374  	b.data += fmt.Sprintf(`
   375  // %s creates a new instance of this resource
   376  func %s(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) {
   377  	res := &%s{
   378  		MqlRuntime: runtime,
   379  	}
   380  
   381  	err := SetAllData(res, args)
   382  	if err != nil {
   383  		return res, err
   384  	}
   385  
   386  	%s
   387  
   388  	if runtime.HasRecording {
   389  		args, err = runtime.ResourceFromRecording(%s, res.__id)
   390  		if err != nil || args == nil {
   391  			return res, err
   392  		}
   393  		return res, SetAllData(res, args)
   394  	}
   395  
   396  	return res, nil
   397  }
   398  
   399  func (c *%s) MqlName() string {
   400  	return "%s"
   401  }
   402  
   403  func (c *%s) MqlID() string {
   404  	return c.__id
   405  }
   406  `,
   407  		createName, createName,
   408  		structName,
   409  		idCode,
   410  		strconv.Quote(r.ID),
   411  		structName, r.ID,
   412  		structName,
   413  	)
   414  }
   415  
   416  func (b *goBuilder) goFields(r *Resource) {
   417  	for i := range r.Body.Fields {
   418  		field := r.Body.Fields[i]
   419  		if field.Init != nil {
   420  			continue
   421  		}
   422  
   423  		b.goField(r, field)
   424  	}
   425  }
   426  
   427  func (b *goBuilder) goStaticField(r *Resource, field *Field) {
   428  	goName := field.BasicField.goName()
   429  	b.data += fmt.Sprintf(`
   430  func (c *%s) Get%s() *plugin.TValue[%s] {
   431  	return &c.%s
   432  }
   433  `,
   434  		r.structName(b), goName, field.BasicField.Type.goType(b),
   435  		goName,
   436  	)
   437  }
   438  
   439  func (b *goBuilder) goField(r *Resource, field *Field) {
   440  	if field.BasicField.isStatic() {
   441  		b.goStaticField(r, field)
   442  		return
   443  	}
   444  
   445  	if field.BasicField.ID == "id" {
   446  		b.errors.Add(errors.New("cannot create a dynamically computed `id` field, please turn it into a static field (ie: `id(..)` => `id`)"))
   447  		return
   448  	}
   449  
   450  	goName := field.BasicField.goName()
   451  	goType := field.BasicField.Type.goType(b)
   452  	goZero := field.BasicField.Type.goZeroValue()
   453  
   454  	argDefs := []string{}
   455  	argCall := []string{}
   456  	if field.BasicField.Args != nil {
   457  		args := field.BasicField.Args.List
   458  		for i := range args {
   459  			arg := args[i]
   460  			name := resource2goname(arg.Type, b)
   461  			argDefs = append(argDefs, fmt.Sprintf(`varg%s := c.Get%s()
   462  		if varg%s.Error != nil {
   463  			return %s, varg%s.Error
   464  		}
   465  
   466  		`, name, name, name, goZero, name))
   467  			argCall = append(argCall, "varg"+name+".Data")
   468  		}
   469  	}
   470  
   471  	// resource types may be loaded from recordings
   472  	var fromRecording string
   473  	if field.BasicField.Type.containsResource(b) {
   474  		fromRecording = fmt.Sprintf(`if c.MqlRuntime.HasRecording {
   475  			d, err := c.MqlRuntime.FieldResourceFromRecording(%s, c.__id, %s)
   476  			if err != nil {
   477  				return %s, err
   478  			}
   479  			if d != nil {
   480  				return d.Value.(%s), nil
   481  			}
   482  		}
   483  
   484  		`,
   485  			strconv.Quote(r.ID), strconv.Quote(field.BasicField.ID),
   486  			goZero,
   487  			goType,
   488  		)
   489  	}
   490  
   491  	b.data += fmt.Sprintf(`
   492  func (c *%s) Get%s() *plugin.TValue[%s] {
   493  	return plugin.GetOrCompute[%s](&c.%s, func() (%s, error) {
   494  		%s%sreturn c.%s(%s)
   495  	})
   496  }
   497  `,
   498  		r.structName(b), goName, goType,
   499  		goType, goName, goType,
   500  		fromRecording, strings.Join(argDefs, ""),
   501  		fieldCall(field.BasicField.ID), strings.Join(argCall, ", "),
   502  	)
   503  }
   504  
   505  // GO METHODS FOR AST
   506  
   507  func (b *goBuilder) importName(symbol string) (string, bool) {
   508  	parts := strings.SplitN(symbol, ".", 2)
   509  	if len(parts) >= 2 {
   510  		if ref, ok := b.ast.imports[parts[0]]; ok {
   511  			if _, ok := ref[parts[1]]; ok {
   512  				return parts[1], true
   513  			}
   514  		}
   515  	}
   516  	return "", false
   517  }
   518  
   519  func indent(s string, depth int) string {
   520  	space := ""
   521  	for i := 0; i < depth; i++ {
   522  		space += "\t"
   523  	}
   524  	return space + strings.Replace(s, "\n", "\n"+space, -1)
   525  }
   526  
   527  func (r *Resource) structName(b *goBuilder) string {
   528  	return "mql" + r.interfaceName(b)
   529  }
   530  
   531  var reMethodName = regexp.MustCompile("\\.[a-z]")
   532  
   533  func capitalizeDot(in []byte) []byte {
   534  	return bytes.ToUpper([]byte{in[1]})
   535  }
   536  
   537  func (r *Resource) interfaceName(b *goBuilder) string {
   538  	return resource2goname(r.ID, b)
   539  }
   540  
   541  func resource2goname(s string, b *goBuilder) string {
   542  	pack := strings.SplitN(s, ".", 2)
   543  	var name string
   544  	if pack[0] != s {
   545  		resources, ok := b.ast.imports[pack[0]]
   546  		if ok {
   547  			if _, ok := resources[pack[1]]; !ok {
   548  				b.errors.Add(errors.New("cannot find resource " + pack[1] + " in imported resource pack " + pack[0]))
   549  			}
   550  			name = pack[0] + "." + strings.Title(string(
   551  				reMethodName.ReplaceAllFunc([]byte(pack[1]), capitalizeDot),
   552  			))
   553  			b.packsInUse[pack[0]] = struct{}{}
   554  		}
   555  	}
   556  	if name == "" {
   557  		name = strings.Title(string(
   558  			reMethodName.ReplaceAllFunc([]byte(s), capitalizeDot),
   559  		))
   560  	}
   561  
   562  	return name
   563  }
   564  
   565  func (b *ResourceDef) staticFields() []*BasicField {
   566  	res := []*BasicField{}
   567  	for _, f := range b.Fields {
   568  		if f.BasicField != nil {
   569  			if f.BasicField.isStatic() {
   570  				res = append(res, f.BasicField)
   571  			}
   572  		}
   573  	}
   574  	return res
   575  }
   576  
   577  func (f *BasicField) goName() string {
   578  	return strings.Title(f.ID)
   579  }
   580  
   581  func (f *BasicField) isStatic() bool {
   582  	return f.Args == nil
   583  }
   584  
   585  func (f *BasicField) methodname() string {
   586  	return strings.Title(f.ID)
   587  }
   588  
   589  // Retrieve the raw mondoo equivalent type, which can be looked up
   590  // as a resource.
   591  func (t *Type) Type(ast *LR) types.Type {
   592  	if t.SimpleType != nil {
   593  		return t.SimpleType.typeItems(ast)
   594  	}
   595  	if t.ListType != nil {
   596  		return t.ListType.typeItems(ast)
   597  	}
   598  	if t.MapType != nil {
   599  		return t.MapType.typeItems(ast)
   600  	}
   601  	return types.Any
   602  }
   603  
   604  func (t *MapType) typeItems(ast *LR) types.Type {
   605  	return types.Map(t.Key.typeItems(ast), t.Value.Type(ast))
   606  }
   607  
   608  func (t *ListType) typeItems(ast *LR) types.Type {
   609  	return types.Array(t.Type.Type(ast))
   610  }
   611  
   612  func (t *SimpleType) typeItems(ast *LR) types.Type {
   613  	switch t.Type {
   614  	case "bool":
   615  		return types.Bool
   616  	case "int":
   617  		return types.Int
   618  	case "float":
   619  		return types.Float
   620  	case "string":
   621  		return types.String
   622  	case "regex":
   623  		return types.Regex
   624  	case "time":
   625  		return types.Time
   626  	case "dict":
   627  		return types.Dict
   628  	default:
   629  		return resourceType(t.Type, ast)
   630  	}
   631  }
   632  
   633  // Try to build an MQL resource from the given name. It may or may not exist in
   634  // a pack. If it doesn't exist at all
   635  func resourceType(name string, ast *LR) types.Type {
   636  	pack := strings.SplitN(name, ".", 2)
   637  	if pack[0] != name {
   638  		resources, ok := ast.imports[pack[0]]
   639  		if ok {
   640  			if _, ok := resources[pack[1]]; ok {
   641  				return types.Resource(pack[1])
   642  			}
   643  		}
   644  	}
   645  
   646  	// TODO: look up resources in the current registry and notify if they are not found
   647  
   648  	return types.Resource(name)
   649  }
   650  
   651  // Retrieve the mondoo equivalent of the type. This is a stringified type
   652  // i.e. it can be compiled with the MQL imports
   653  func (t *Type) mondooType(b *goBuilder) string {
   654  	i := t.mondooTypeItems(b)
   655  	if i == "" {
   656  		return "NO_TYPE_DETECTED"
   657  	}
   658  	return i
   659  }
   660  
   661  func (t *Type) mondooTypeItems(b *goBuilder) string {
   662  	if t.SimpleType != nil {
   663  		return t.SimpleType.mondooTypeItems(b)
   664  	}
   665  	if t.ListType != nil {
   666  		return t.ListType.mondooTypeItems(b)
   667  	}
   668  	if t.MapType != nil {
   669  		return t.MapType.mondooTypeItems(b)
   670  	}
   671  	return ""
   672  }
   673  
   674  func (t *MapType) mondooTypeItems(b *goBuilder) string {
   675  	return "types.Map(" + t.Key.mondooTypeItems(b) + ", " + t.Value.mondooTypeItems(b) + ")"
   676  }
   677  
   678  func (t *ListType) mondooTypeItems(b *goBuilder) string {
   679  	return "types.Array(" + t.Type.mondooTypeItems(b) + ")"
   680  }
   681  
   682  func (t *SimpleType) mondooTypeItems(b *goBuilder) string {
   683  	switch t.Type {
   684  	case "bool":
   685  		return "types.Bool"
   686  	case "int":
   687  		return "types.Int"
   688  	case "float":
   689  		return "types.Float"
   690  	case "string":
   691  		return "types.String"
   692  	case "regex":
   693  		return "types.Regex"
   694  	case "time":
   695  		return "types.Time"
   696  	case "dict":
   697  		return "types.Dict"
   698  	default:
   699  		if name, ok := b.importName(t.Type); ok {
   700  			return "types.Resource(\"" + name + "\")"
   701  		}
   702  		return "types.Resource(\"" + t.Type + "\")"
   703  	}
   704  
   705  	// TODO: check that this type if a proper resource
   706  	// panic("Cannot convert type '" + t.Type + "' to mondoo type")
   707  }
   708  
   709  func (t *Type) containsResource(b *goBuilder) bool {
   710  	if t.ListType != nil {
   711  		return t.ListType.Type.containsResource(b)
   712  	}
   713  	if t.MapType != nil {
   714  		return t.MapType.Value.containsResource(b)
   715  	}
   716  	if t.SimpleType != nil {
   717  		if _, ok := primitiveTypes[t.SimpleType.Type]; !ok {
   718  			return true
   719  		}
   720  	}
   721  	return false
   722  }
   723  
   724  // The go type is the golang-equivalent code type, i.e. the type of the
   725  // actual objects that are being moved around.
   726  func (t *Type) goType(b *goBuilder) string {
   727  	if t.SimpleType != nil {
   728  		return t.SimpleType.goType(b)
   729  	}
   730  	if t.ListType != nil {
   731  		return t.ListType.goType()
   732  	}
   733  	if t.MapType != nil {
   734  		return t.MapType.goType(b)
   735  	}
   736  	return "NO_TYPE_DETECTED"
   737  }
   738  
   739  func (t *MapType) goType(b *goBuilder) string {
   740  	// limited to interface{} because we cannot cast as universally
   741  	// between types yet
   742  	return "map[" + t.Key.goType(b) + "]interface{}"
   743  }
   744  
   745  func (t *ListType) goType() string {
   746  	// limited to []interface{} because we cannot cast as universally
   747  	// between types yet
   748  	return "[]interface{}"
   749  }
   750  
   751  var primitiveTypes = map[string]string{
   752  	"string": "string",
   753  	"bool":   "bool",
   754  	"int":    "int64",
   755  	"float":  "float64",
   756  	"time":   "*time.Time",
   757  	"regex":  "string",
   758  	"dict":   "interface{}",
   759  	"any":    "interface{}",
   760  }
   761  
   762  func (t *SimpleType) goType(b *goBuilder) string {
   763  	pt, ok := primitiveTypes[t.Type]
   764  	if ok {
   765  		return pt
   766  	}
   767  
   768  	if _, ok := b.importName(t.Type); ok {
   769  		return "plugin.Resource"
   770  	}
   771  
   772  	return "*mql" + resource2goname(t.Type, b)
   773  }
   774  
   775  func (t *Type) goZeroValue() string {
   776  	if t.SimpleType != nil {
   777  		return t.SimpleType.goZeroValue()
   778  	}
   779  	return "nil"
   780  }
   781  
   782  var primitiveZeros = map[string]string{
   783  	"string": "\"\"",
   784  	"bool":   "false",
   785  	"int":    "0",
   786  	"float":  "0.0",
   787  	"time":   "nil",
   788  	"dict":   "nil",
   789  	"any":    "nil",
   790  }
   791  
   792  func (t *SimpleType) goZeroValue() string {
   793  	pt, ok := primitiveZeros[t.Type]
   794  	if ok {
   795  		return pt
   796  	}
   797  
   798  	// TODO: check if the resource exists
   799  	return "nil"
   800  }