backend/core/models/dynamic_tabler.go (139 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package models import ( "encoding/json" "reflect" "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/dal" ) // DynamicTabler is a core.Tabler that wraps a runtime (anonymously) generated data-model. Due to limitations of // reflection in Go and the GORM framework, the underlying model and the table have to be explicitly passed into dal.Dal's API // via Unwrap() and TableName() type DynamicTabler interface { dal.Tabler json.Marshaler json.Unmarshaler NewValue() any New() DynamicTabler NewSlice() DynamicTabler From(src any) errors.Error To(target any) errors.Error Unwrap() any UnwrapPtr() *any UnwrapSlice() []any } // DynamicTablerImpl the implementation of DynamicTabler type DynamicTablerImpl struct { objType reflect.Type wrapped any table string } func NewDynamicTabler(tableName string, objType reflect.Type) DynamicTabler { return &DynamicTablerImpl{ objType: objType, table: tableName, } } func (d *DynamicTablerImpl) NewValue() any { return reflect.New(d.objType).Interface() } func (d *DynamicTablerImpl) New() DynamicTabler { return &DynamicTablerImpl{ objType: d.objType, wrapped: d.NewValue(), table: d.table, } } func (d *DynamicTablerImpl) NewSlice() DynamicTabler { sliceType := reflect.SliceOf(d.objType) return &DynamicTablerImpl{ objType: sliceType, wrapped: reflect.New(sliceType).Interface(), table: d.table, } } func (d *DynamicTablerImpl) From(src any) errors.Error { b, err := json.Marshal(src) if err != nil { return errors.Convert(err) } return errors.Convert(json.Unmarshal(b, d.wrapped)) } func (d *DynamicTablerImpl) To(target any) errors.Error { b, err := json.Marshal(d.wrapped) if err != nil { return errors.Convert(err) } return errors.Convert(json.Unmarshal(b, target)) } func (d *DynamicTablerImpl) Unwrap() any { return d.wrapped } func (d *DynamicTablerImpl) UnwrapPtr() *any { return &d.wrapped } func (d *DynamicTablerImpl) UnwrapSlice() []any { var arr []any slice := reflect.ValueOf(d.wrapped).Elem() for i := 0; i < slice.Len(); i++ { arr = append(arr, slice.Index(i).Interface()) } return arr } func (d *DynamicTablerImpl) TableName() string { return d.table } func (d *DynamicTablerImpl) MarshalJSON() ([]byte, error) { return json.Marshal(d.wrapped) } func (d *DynamicTablerImpl) UnmarshalJSON(b []byte) error { // Insert the string directly into the Data member return json.Unmarshal(b, &d.wrapped) } var _ DynamicTabler = (*DynamicTablerImpl)(nil) // UnwrapObject if the actual object is wrapped in some proxy, it unwinds and returns it, otherwise this is idempotent func UnwrapObject(ifc any) any { if dynamic, ok := ifc.(DynamicTabler); ok { return dynamic.Unwrap() } return ifc } // DumpInfo Useful function for debugging purposes - to see what's in the struct at runtime func DumpInfo(tbl DynamicTabler) map[string]any { typ := reflect.TypeOf(tbl.Unwrap()) type typeInfo struct { Type string Tags string } var fn func(t reflect.Type) map[string]any fn = func(t reflect.Type) map[string]any { infoMap := map[string]any{} if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() != reflect.Struct { return infoMap } for i := 0; i < t.NumField(); i++ { f := t.Field(i) if f.Anonymous { subMap := fn(f.Type) infoMap[f.Name] = subMap } else { if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() == reflect.Struct { subMap := fn(f.Type) if len(subMap) == 0 { infoMap[f.Name] = typeInfo{ Type: f.Type.Name(), Tags: string(f.Tag), } } else { infoMap[f.Name] = subMap } } else { infoMap[f.Name] = typeInfo{ Type: f.Type.Name(), Tags: string(f.Tag), } } } } return infoMap } return fn(typ) }