| /* |
| 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) |
| } |