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 }