github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/rego/schemas/builder.go (about)

     1  package schemas
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/khulnasoft-lab/defsec/pkg/rego/convert"
     9  	"github.com/khulnasoft-lab/defsec/pkg/state"
    10  )
    11  
    12  type RawSchema struct {
    13  	Type       string               `json:"type"` // object
    14  	Properties map[string]Property  `json:"properties,omitempty"`
    15  	Defs       map[string]*Property `json:"definitions,omitempty"`
    16  }
    17  
    18  type Property struct {
    19  	Type       string              `json:"type,omitempty"`
    20  	Ref        string              `json:"$ref,omitempty"`
    21  	Properties map[string]Property `json:"properties,omitempty"`
    22  	Items      *Property           `json:"items,omitempty"`
    23  }
    24  
    25  type builder struct {
    26  	schema RawSchema
    27  }
    28  
    29  func Build() (*RawSchema, error) {
    30  
    31  	b := newBuilder()
    32  
    33  	inputValue := reflect.ValueOf(state.State{})
    34  
    35  	err := b.fromInput(inputValue)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	return &b.schema, nil
    41  }
    42  
    43  func newBuilder() *builder {
    44  	return &builder{
    45  		schema: RawSchema{
    46  			Properties: nil,
    47  			Defs:       nil,
    48  		},
    49  	}
    50  }
    51  
    52  func (b *builder) fromInput(inputValue reflect.Value) error {
    53  
    54  	prop, err := b.readProperty("", nil, inputValue.Type(), 0)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	if prop == nil {
    59  		return fmt.Errorf("property is nil")
    60  	}
    61  	b.schema.Properties = prop.Properties
    62  	b.schema.Type = prop.Type
    63  	return nil
    64  }
    65  
    66  func refName(name string, parent, t reflect.Type) string {
    67  	if t.Name() == "" { // inline struct
    68  		return sanitise(parent.PkgPath() + "." + parent.Name() + "." + name)
    69  	}
    70  	return sanitise(t.PkgPath() + "." + t.Name())
    71  }
    72  
    73  func sanitise(s string) string {
    74  	return strings.ReplaceAll(s, "/", ".")
    75  }
    76  
    77  func (b *builder) readProperty(name string, parent, inputType reflect.Type, indent int) (*Property, error) {
    78  
    79  	if inputType.Kind() == reflect.Ptr {
    80  		inputType = inputType.Elem()
    81  	}
    82  
    83  	switch inputType.String() {
    84  	case "types.Metadata", "types.Range", "types.Reference":
    85  		return nil, nil
    86  	}
    87  
    88  	if b.schema.Defs != nil {
    89  		_, ok := b.schema.Defs[refName(name, parent, inputType)]
    90  		if ok {
    91  			return &Property{
    92  				Type: "object",
    93  				Ref:  "#/definitions/" + refName(name, parent, inputType),
    94  			}, nil
    95  		}
    96  	}
    97  
    98  	fmt.Println(strings.Repeat("  ", indent) + name)
    99  
   100  	switch kind := inputType.Kind(); kind {
   101  	case reflect.Struct:
   102  		return b.readStruct(name, parent, inputType, indent)
   103  	case reflect.Slice:
   104  		return b.readSlice(name, parent, inputType, indent)
   105  	case reflect.String:
   106  		return &Property{
   107  			Type: "string",
   108  		}, nil
   109  	case reflect.Int:
   110  		return &Property{
   111  			Type: "integer",
   112  		}, nil
   113  	case reflect.Bool:
   114  		return &Property{
   115  			Type: "boolean",
   116  		}, nil
   117  	case reflect.Float32, reflect.Float64:
   118  		return &Property{
   119  			Type: "number",
   120  		}, nil
   121  	}
   122  
   123  	switch inputType.Name() {
   124  	case "BoolValue":
   125  		return &Property{
   126  			Type: "object",
   127  			Properties: map[string]Property{
   128  				"value": {
   129  					Type: "boolean",
   130  				},
   131  			},
   132  		}, nil
   133  	case "IntValue":
   134  		return &Property{
   135  			Type: "object",
   136  			Properties: map[string]Property{
   137  				"value": {
   138  					Type: "integer",
   139  				},
   140  			},
   141  		}, nil
   142  	case "StringValue", "TimeValue", "BytesValue":
   143  		return &Property{
   144  			Type: "object",
   145  			Properties: map[string]Property{
   146  				"value": {
   147  					Type: "string",
   148  				},
   149  			},
   150  		}, nil
   151  	case "MapValue":
   152  		return &Property{
   153  			Type: "object",
   154  			Properties: map[string]Property{
   155  				"value": {
   156  					Type: "object",
   157  				},
   158  			},
   159  		}, nil
   160  
   161  	}
   162  
   163  	fmt.Printf("WARNING: unsupported type: %s (%s)\n", inputType.Name(), inputType)
   164  	return nil, nil
   165  }
   166  
   167  var converterInterface = reflect.TypeOf((*convert.Converter)(nil)).Elem()
   168  
   169  func (b *builder) readStruct(name string, parent, inputType reflect.Type, indent int) (*Property, error) {
   170  
   171  	if b.schema.Defs == nil {
   172  		b.schema.Defs = map[string]*Property{}
   173  	}
   174  
   175  	def := &Property{
   176  		Type:       "object",
   177  		Properties: map[string]Property{},
   178  	}
   179  
   180  	if parent != nil {
   181  		b.schema.Defs[refName(name, parent, inputType)] = def
   182  	}
   183  
   184  	if inputType.Implements(converterInterface) {
   185  		if inputType.Kind() == reflect.Ptr {
   186  			inputType = inputType.Elem()
   187  		}
   188  		returns := reflect.New(inputType).MethodByName("ToRego").Call(nil)
   189  		if err := b.readRego(def, name, parent, returns[0].Type(), returns[0].Interface(), indent); err != nil {
   190  			return nil, err
   191  		}
   192  	} else {
   193  
   194  		for i := 0; i < inputType.NumField(); i++ {
   195  			field := inputType.Field(i)
   196  			prop, err := b.readProperty(field.Name, inputType, field.Type, indent+1)
   197  			if err != nil {
   198  				return nil, err
   199  			}
   200  			if prop == nil {
   201  				continue
   202  			}
   203  			key := strings.ToLower(field.Name)
   204  			if key == "metadata" {
   205  				continue
   206  			}
   207  			def.Properties[key] = *prop
   208  		}
   209  	}
   210  
   211  	if parent == nil {
   212  		return def, nil
   213  	}
   214  
   215  	return &Property{
   216  		Type: "object",
   217  		Ref:  "#/definitions/" + refName(name, parent, inputType),
   218  	}, nil
   219  }
   220  
   221  func (b *builder) readSlice(name string, parent, inputType reflect.Type, indent int) (*Property, error) {
   222  
   223  	items, err := b.readProperty(name, parent, inputType.Elem(), indent+1)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	prop := &Property{
   229  		Type:  "array",
   230  		Items: items,
   231  	}
   232  	return prop, nil
   233  }
   234  
   235  func (b *builder) readRego(def *Property, name string, parent reflect.Type, typ reflect.Type, raw interface{}, indent int) error {
   236  
   237  	switch cast := raw.(type) {
   238  	case map[string]interface{}:
   239  		def.Type = "object"
   240  		for k, v := range cast {
   241  			child := &Property{
   242  				Properties: map[string]Property{},
   243  			}
   244  			if err := b.readRego(child, k, reflect.TypeOf(raw), reflect.TypeOf(v), v, indent+1); err != nil {
   245  				return err
   246  			}
   247  			def.Properties[k] = *child
   248  		}
   249  	case map[string]string:
   250  		def.Type = "object"
   251  		for k, v := range cast {
   252  			child := &Property{
   253  				Properties: map[string]Property{},
   254  			}
   255  			if err := b.readRego(child, k, reflect.TypeOf(raw), reflect.TypeOf(v), v, indent+1); err != nil {
   256  				return err
   257  			}
   258  			def.Properties[k] = *child
   259  		}
   260  	default:
   261  		prop, err := b.readProperty(name, parent, typ, indent)
   262  		if err != nil {
   263  			return err
   264  		}
   265  		*def = *prop
   266  	}
   267  
   268  	return nil
   269  
   270  }