mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
port to electron (#33)
This commit is contained in:
@@ -9,12 +9,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/servicemeta"
|
||||
)
|
||||
|
||||
type BlockService struct{}
|
||||
|
||||
const DefaultTimeout = 2 * time.Second
|
||||
|
||||
func (bs *BlockService) SendCommand_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
Desc: "send command to block",
|
||||
ArgNames: []string{"blockid", "command"},
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *BlockService) SendCommand(blockId string, cmdMap map[string]any) error {
|
||||
cmd, err := blockcontroller.ParseCmdMap(cmdMap)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/servicemeta"
|
||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wstore"
|
||||
)
|
||||
@@ -28,7 +29,14 @@ func parseORef(oref string) (*waveobj.ORef, error) {
|
||||
return &waveobj.ORef{OType: fields[0], OID: fields[1]}, nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObject(orefStr string) (any, error) {
|
||||
func (svc *ObjectService) GetObject_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
Desc: "get wave object by oref",
|
||||
ArgNames: []string{"oref"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObject(orefStr string) (waveobj.WaveObj, error) {
|
||||
oref, err := parseORef(orefStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -39,11 +47,17 @@ func (svc *ObjectService) GetObject(orefStr string) (any, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting object: %w", err)
|
||||
}
|
||||
rtn, err := waveobj.ToJsonMap(obj)
|
||||
return rtn, err
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObjects(orefStrArr []string) (any, error) {
|
||||
func (svc *ObjectService) GetObjects_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"orefs"},
|
||||
ReturnDesc: "objects",
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObjects(orefStrArr []string) ([]waveobj.WaveObj, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
|
||||
@@ -78,30 +92,41 @@ func updatesRtn(ctx context.Context, rtnVal map[string]any) (any, error) {
|
||||
return rtnVal, nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName string, activateTab bool) (any, error) {
|
||||
func (svc *ObjectService) AddTabToWorkspace_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "tabName", "activateTab"},
|
||||
ReturnDesc: "tabId",
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName string, activateTab bool) (string, wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
windowData, err := wstore.DBMustGet[*wstore.Window](ctx, uiContext.WindowId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting window: %w", err)
|
||||
return "", nil, fmt.Errorf("error getting window: %w", err)
|
||||
}
|
||||
tab, err := wstore.CreateTab(ctx, windowData.WorkspaceId, tabName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating tab: %w", err)
|
||||
return "", nil, fmt.Errorf("error creating tab: %w", err)
|
||||
}
|
||||
if activateTab {
|
||||
err = wstore.SetActiveTab(ctx, uiContext.WindowId, tab.OID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting active tab: %w", err)
|
||||
return "", nil, fmt.Errorf("error setting active tab: %w", err)
|
||||
}
|
||||
}
|
||||
rtn := make(map[string]any)
|
||||
rtn["tabid"] = waveobj.GetOID(tab)
|
||||
return updatesRtn(ctx, rtn)
|
||||
return tab.OID, wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) (any, error) {
|
||||
func (svc *ObjectService) SetActiveTab_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "tabId"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) (wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
@@ -122,32 +147,51 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return updatesRtn(ctx, nil)
|
||||
blockORefs := tab.GetBlockORefs()
|
||||
blocks, err := wstore.DBSelectORefs(ctx, blockORefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting tab blocks: %w", err)
|
||||
}
|
||||
updates := wstore.ContextGetUpdatesRtn(ctx)
|
||||
updates = append(updates, wstore.MakeUpdate(tab))
|
||||
updates = append(updates, wstore.MakeUpdates(blocks)...)
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (any, error) {
|
||||
func (svc *ObjectService) CreateBlock_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "blockDef", "rtOpts"},
|
||||
ReturnDesc: "blockId",
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (string, wstore.UpdatesRtnType, error) {
|
||||
if uiContext.ActiveTabId == "" {
|
||||
return nil, fmt.Errorf("no active tab")
|
||||
return "", nil, fmt.Errorf("no active tab")
|
||||
}
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
blockData, err := wstore.CreateBlock(ctx, uiContext.ActiveTabId, blockDef, rtOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating block: %w", err)
|
||||
return "", nil, fmt.Errorf("error creating block: %w", err)
|
||||
}
|
||||
if blockData.Controller != "" {
|
||||
err = blockcontroller.StartBlockController(ctx, blockData.OID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error starting block controller: %w", err)
|
||||
return "", nil, fmt.Errorf("error starting block controller: %w", err)
|
||||
}
|
||||
}
|
||||
rtn := make(map[string]any)
|
||||
rtn["blockId"] = blockData.OID
|
||||
return updatesRtn(ctx, rtn)
|
||||
return blockData.OID, wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string) (any, error) {
|
||||
func (svc *ObjectService) DeleteBlock_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "blockId"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string) (wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
@@ -156,10 +200,16 @@ func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string
|
||||
return nil, fmt.Errorf("error deleting block: %w", err)
|
||||
}
|
||||
blockcontroller.StopBlockController(blockId)
|
||||
return updatesRtn(ctx, nil)
|
||||
return wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (any, error) {
|
||||
func (svc *ObjectService) CloseTab_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "tabId"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
@@ -191,10 +241,16 @@ func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (an
|
||||
}
|
||||
wstore.SetActiveTab(ctx, uiContext.WindowId, newActiveTabId)
|
||||
}
|
||||
return updatesRtn(ctx, nil)
|
||||
return wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr string, meta map[string]any) (any, error) {
|
||||
func (svc *ObjectService) UpdateObjectMeta_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "oref", "meta"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr string, meta map[string]any) (wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
@@ -206,18 +262,23 @@ func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr s
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updateing %q meta: %w", orefStr, err)
|
||||
}
|
||||
return updatesRtn(ctx, nil)
|
||||
return wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) UpdateObject(uiContext wstore.UIContext, objData map[string]any, returnUpdates bool) (any, error) {
|
||||
func (svc *ObjectService) UpdateObject_Meta() servicemeta.MethodMeta {
|
||||
return servicemeta.MethodMeta{
|
||||
ArgNames: []string{"uiContext", "waveObj", "returnUpdates"},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *ObjectService) UpdateObject(uiContext wstore.UIContext, waveObj waveobj.WaveObj, returnUpdates bool) (wstore.UpdatesRtnType, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
ctx = wstore.ContextWithUpdates(ctx)
|
||||
|
||||
oref, err := waveobj.ORefFromMap(objData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("objData is not a valid object, requires otype and oid: %w", err)
|
||||
if waveObj == nil {
|
||||
return nil, fmt.Errorf("update wavobj is nil")
|
||||
}
|
||||
oref := waveobj.ORefFromWaveObj(waveObj)
|
||||
found, err := wstore.DBExistsORef(ctx, *oref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting object: %w", err)
|
||||
@@ -225,16 +286,12 @@ func (svc *ObjectService) UpdateObject(uiContext wstore.UIContext, objData map[s
|
||||
if !found {
|
||||
return nil, fmt.Errorf("object not found: %s", oref)
|
||||
}
|
||||
newObj, err := waveobj.FromJsonMap(objData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting data to valid wave object: %w", err)
|
||||
}
|
||||
err = wstore.DBUpdate(ctx, newObj)
|
||||
err = wstore.DBUpdate(ctx, waveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating object: %w", err)
|
||||
}
|
||||
if returnUpdates {
|
||||
return updatesRtn(ctx, nil)
|
||||
return wstore.ContextGetUpdatesRtn(ctx), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/blockservice"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/clientservice"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/fileservice"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/objectservice"
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/servicemeta"
|
||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wstore"
|
||||
)
|
||||
|
||||
var ServiceMap = map[string]any{
|
||||
"block": &blockservice.BlockService{},
|
||||
"object": &objectservice.ObjectService{},
|
||||
"file": &fileservice.FileService{},
|
||||
"client": &clientservice.ClientService{},
|
||||
}
|
||||
|
||||
var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
var errorRType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
var updatesRType = reflect.TypeOf(([]wstore.WaveObjUpdate{}))
|
||||
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
|
||||
var waveObjSliceRType = reflect.TypeOf([]waveobj.WaveObj{})
|
||||
var waveObjMapRType = reflect.TypeOf(map[string]waveobj.WaveObj{})
|
||||
var methodMetaRType = reflect.TypeOf(servicemeta.MethodMeta{})
|
||||
var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{})
|
||||
var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
|
||||
|
||||
type WebCallType struct {
|
||||
Service string `json:"service"`
|
||||
Method string `json:"method"`
|
||||
UIContext *wstore.UIContext `json:"uicontext,omitempty"`
|
||||
Args []any `json:"args"`
|
||||
}
|
||||
|
||||
type WebReturnType struct {
|
||||
Success bool `json:"success,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Data any `json:"data,omitempty"`
|
||||
Updates []wstore.WaveObjUpdate `json:"updates,omitempty"`
|
||||
}
|
||||
|
||||
func convertNumber(argType reflect.Type, jsonArg float64) (any, error) {
|
||||
switch argType.Kind() {
|
||||
case reflect.Int:
|
||||
return int(jsonArg), nil
|
||||
case reflect.Int8:
|
||||
return int8(jsonArg), nil
|
||||
case reflect.Int16:
|
||||
return int16(jsonArg), nil
|
||||
case reflect.Int32:
|
||||
return int32(jsonArg), nil
|
||||
case reflect.Int64:
|
||||
return int64(jsonArg), nil
|
||||
case reflect.Uint:
|
||||
return uint(jsonArg), nil
|
||||
case reflect.Uint8:
|
||||
return uint8(jsonArg), nil
|
||||
case reflect.Uint16:
|
||||
return uint16(jsonArg), nil
|
||||
case reflect.Uint32:
|
||||
return uint32(jsonArg), nil
|
||||
case reflect.Uint64:
|
||||
return uint64(jsonArg), nil
|
||||
case reflect.Float32:
|
||||
return float32(jsonArg), nil
|
||||
case reflect.Float64:
|
||||
return jsonArg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid number type %s", argType)
|
||||
}
|
||||
|
||||
func convertComplex(argType reflect.Type, jsonArg any) (any, error) {
|
||||
nativeArgVal := reflect.New(argType)
|
||||
err := waveobj.DoMapStucture(nativeArgVal.Interface(), jsonArg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nativeArgVal.Elem().Interface(), nil
|
||||
}
|
||||
|
||||
func isSpecialWaveArgType(argType reflect.Type) bool {
|
||||
return argType == waveObjRType || argType == waveObjSliceRType || argType == waveObjMapRType
|
||||
}
|
||||
|
||||
func convertSpecial(argType reflect.Type, jsonArg any) (any, error) {
|
||||
jsonType := reflect.TypeOf(jsonArg)
|
||||
if argType == waveObjRType {
|
||||
if jsonType.Kind() != reflect.Map {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
return waveobj.FromJsonMap(jsonArg.(map[string]any))
|
||||
} else if argType == waveObjSliceRType {
|
||||
if jsonType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
sliceArg := jsonArg.([]any)
|
||||
nativeSlice := make([]waveobj.WaveObj, len(sliceArg))
|
||||
for idx, elem := range sliceArg {
|
||||
elemMap, ok := elem.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s (idx %d is not a map, is %T)", jsonArg, waveObjSliceRType, idx, elem)
|
||||
}
|
||||
nativeObj, err := waveobj.FromJsonMap(elemMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s (idx %d) error: %v", jsonArg, waveObjSliceRType, idx, err)
|
||||
}
|
||||
nativeSlice[idx] = nativeObj
|
||||
}
|
||||
return nativeSlice, nil
|
||||
} else if argType == waveObjMapRType {
|
||||
if jsonType.Kind() != reflect.Map {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
mapArg := jsonArg.(map[string]any)
|
||||
nativeMap := make(map[string]waveobj.WaveObj)
|
||||
for key, elem := range mapArg {
|
||||
elemMap, ok := elem.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s (key %s is not a map, is %T)", jsonArg, waveObjMapRType, key, elem)
|
||||
}
|
||||
nativeObj, err := waveobj.FromJsonMap(elemMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s (key %s) error: %v", jsonArg, waveObjMapRType, key, err)
|
||||
}
|
||||
nativeMap[key] = nativeObj
|
||||
}
|
||||
return nativeMap, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid special wave argument type %s", argType)
|
||||
}
|
||||
}
|
||||
|
||||
func convertSpecialForReturn(argType reflect.Type, nativeArg any) (any, error) {
|
||||
if argType == waveObjRType {
|
||||
return waveobj.ToJsonMap(nativeArg.(waveobj.WaveObj))
|
||||
} else if argType == waveObjSliceRType {
|
||||
nativeSlice := nativeArg.([]waveobj.WaveObj)
|
||||
jsonSlice := make([]map[string]any, len(nativeSlice))
|
||||
for idx, elem := range nativeSlice {
|
||||
elemMap, err := waveobj.ToJsonMap(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonSlice[idx] = elemMap
|
||||
}
|
||||
return jsonSlice, nil
|
||||
} else if argType == waveObjMapRType {
|
||||
nativeMap := nativeArg.(map[string]waveobj.WaveObj)
|
||||
jsonMap := make(map[string]map[string]any)
|
||||
for key, elem := range nativeMap {
|
||||
elemMap, err := waveobj.ToJsonMap(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonMap[key] = elemMap
|
||||
}
|
||||
return jsonMap, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid special wave argument type %s", argType)
|
||||
}
|
||||
}
|
||||
|
||||
func convertArgument(argType reflect.Type, jsonArg any) (any, error) {
|
||||
if jsonArg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if isSpecialWaveArgType(argType) {
|
||||
return convertSpecial(argType, jsonArg)
|
||||
}
|
||||
jsonType := reflect.TypeOf(jsonArg)
|
||||
switch argType.Kind() {
|
||||
case reflect.String:
|
||||
if jsonType.Kind() == reflect.String {
|
||||
return jsonArg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
|
||||
case reflect.Bool:
|
||||
if jsonType.Kind() == reflect.Bool {
|
||||
return jsonArg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
if jsonType.Kind() == reflect.Float64 {
|
||||
return convertNumber(argType, jsonArg.(float64))
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
|
||||
case reflect.Map:
|
||||
if argType.Key().Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("invalid map key type %s", argType.Key())
|
||||
}
|
||||
if jsonType.Kind() != reflect.Map {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
return convertComplex(argType, jsonArg)
|
||||
|
||||
case reflect.Slice:
|
||||
if jsonType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
return convertComplex(argType, jsonArg)
|
||||
|
||||
case reflect.Struct:
|
||||
if jsonType.Kind() != reflect.Map {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
return convertComplex(argType, jsonArg)
|
||||
|
||||
case reflect.Ptr:
|
||||
if argType.Elem().Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("invalid pointer type %s", argType)
|
||||
}
|
||||
if jsonType.Kind() != reflect.Map {
|
||||
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
|
||||
}
|
||||
return convertComplex(argType, jsonArg)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid argument type %s", argType)
|
||||
}
|
||||
}
|
||||
|
||||
func isNilable(val reflect.Value) bool {
|
||||
switch val.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan, reflect.Func:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func convertReturnValues(rtnVals []reflect.Value) *WebReturnType {
|
||||
rtn := &WebReturnType{}
|
||||
if len(rtnVals) == 0 {
|
||||
return rtn
|
||||
}
|
||||
for _, val := range rtnVals {
|
||||
if isNilable(val) && val.IsNil() {
|
||||
continue
|
||||
}
|
||||
valType := val.Type()
|
||||
if valType == errorRType {
|
||||
rtn.Error = val.Interface().(error).Error()
|
||||
continue
|
||||
}
|
||||
if valType == updatesRType {
|
||||
// has a special MarshalJSON method
|
||||
rtn.Updates = val.Interface().([]wstore.WaveObjUpdate)
|
||||
continue
|
||||
}
|
||||
if isSpecialWaveArgType(valType) {
|
||||
jsonVal, err := convertSpecialForReturn(valType, val.Interface())
|
||||
if err != nil {
|
||||
rtn.Error = fmt.Errorf("cannot convert special return value: %v", err).Error()
|
||||
continue
|
||||
}
|
||||
rtn.Data = jsonVal
|
||||
continue
|
||||
}
|
||||
rtn.Data = val.Interface()
|
||||
}
|
||||
if rtn.Error == "" {
|
||||
rtn.Success = true
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func webErrorRtn(err error) *WebReturnType {
|
||||
return &WebReturnType{
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func CallService(ctx context.Context, webCall WebCallType) *WebReturnType {
|
||||
svcObj := ServiceMap[webCall.Service]
|
||||
if svcObj == nil {
|
||||
return webErrorRtn(fmt.Errorf("invalid service: %q", webCall.Service))
|
||||
}
|
||||
method := reflect.ValueOf(svcObj).MethodByName(webCall.Method)
|
||||
if !method.IsValid() {
|
||||
return webErrorRtn(fmt.Errorf("invalid method: %s.%s", webCall.Service, webCall.Method))
|
||||
}
|
||||
var valueArgs []reflect.Value
|
||||
argIdx := 0
|
||||
for idx := 0; idx < method.Type().NumIn(); idx++ {
|
||||
argType := method.Type().In(idx)
|
||||
if idx == 0 && argType == contextRType {
|
||||
valueArgs = append(valueArgs, reflect.ValueOf(ctx))
|
||||
continue
|
||||
}
|
||||
if argType == uiContextRType {
|
||||
if webCall.UIContext == nil {
|
||||
return webErrorRtn(fmt.Errorf("missing UIContext for %s.%s", webCall.Service, webCall.Method))
|
||||
}
|
||||
valueArgs = append(valueArgs, reflect.ValueOf(*webCall.UIContext))
|
||||
continue
|
||||
}
|
||||
if argIdx >= len(webCall.Args) {
|
||||
return webErrorRtn(fmt.Errorf("not enough arguments passed %s.%s idx:%d (type %T)", webCall.Service, webCall.Method, idx, argType))
|
||||
}
|
||||
nativeArg, err := convertArgument(argType, webCall.Args[argIdx])
|
||||
if err != nil {
|
||||
return webErrorRtn(fmt.Errorf("cannot convert argument %s.%s type:%T idx:%d error:%v", webCall.Service, webCall.Method, argType, idx, err))
|
||||
}
|
||||
valueArgs = append(valueArgs, reflect.ValueOf(nativeArg))
|
||||
argIdx++
|
||||
}
|
||||
retValArr := method.Call(valueArgs)
|
||||
return convertReturnValues(retValArr)
|
||||
}
|
||||
|
||||
// ValidateServiceArg validates the argument type for a service method
|
||||
// does not allow interfaces (and the obvious invalid types)
|
||||
// arguments + return values have special handling for wave objects
|
||||
func baseValidateServiceArg(argType reflect.Type) error {
|
||||
if argType == waveObjUpdateRType {
|
||||
// has special MarshalJSON method, so it is safe
|
||||
return nil
|
||||
}
|
||||
switch argType.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.Array:
|
||||
return baseValidateServiceArg(argType.Elem())
|
||||
case reflect.Map:
|
||||
if argType.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("invalid map key type %s", argType.Key())
|
||||
}
|
||||
return baseValidateServiceArg(argType.Elem())
|
||||
case reflect.Struct:
|
||||
for idx := 0; idx < argType.NumField(); idx++ {
|
||||
if err := baseValidateServiceArg(argType.Field(idx).Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Interface:
|
||||
return fmt.Errorf("invalid argument type %s: contains interface", argType)
|
||||
|
||||
case reflect.Chan, reflect.Func, reflect.Complex128, reflect.Complex64, reflect.Invalid, reflect.Uintptr, reflect.UnsafePointer:
|
||||
return fmt.Errorf("invalid argument type %s", argType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMethodReturnArg(retType reflect.Type) error {
|
||||
// specifically allow waveobj.WaveObj, []waveobj.WaveObj, map[string]waveobj.WaveObj, and error
|
||||
if isSpecialWaveArgType(retType) || retType == errorRType {
|
||||
return nil
|
||||
}
|
||||
return baseValidateServiceArg(retType)
|
||||
}
|
||||
|
||||
func validateMethodArg(argType reflect.Type) error {
|
||||
// specifically allow waveobj.WaveObj, []waveobj.WaveObj, map[string]waveobj.WaveObj, and context.Context
|
||||
if isSpecialWaveArgType(argType) || argType == contextRType {
|
||||
return nil
|
||||
}
|
||||
return baseValidateServiceArg(argType)
|
||||
}
|
||||
|
||||
func validateServiceMethod(service string, method reflect.Method) error {
|
||||
for idx := 0; idx < method.Type.NumOut(); idx++ {
|
||||
if err := validateMethodReturnArg(method.Type.Out(idx)); err != nil {
|
||||
return fmt.Errorf("invalid return type %s.%s %s: %v", service, method.Name, method.Type.Out(idx), err)
|
||||
}
|
||||
}
|
||||
for idx := 1; idx < method.Type.NumIn(); idx++ {
|
||||
// skip the first argument which is the receiver
|
||||
if err := validateMethodArg(method.Type.In(idx)); err != nil {
|
||||
return fmt.Errorf("invalid argument type %s.%s %s: %v", service, method.Name, method.Type.In(idx), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateServiceMetaMethod(service string, method reflect.Method) error {
|
||||
if method.Type.NumIn() != 1 {
|
||||
return fmt.Errorf("invalid number of arguments %s.%s: got:%d, expected just the receiver", service, method.Name, method.Type.NumIn())
|
||||
}
|
||||
if method.Type.NumOut() != 1 && method.Type.Out(0) != methodMetaRType {
|
||||
return fmt.Errorf("invalid return type %s.%s: got:%s, expected servicemeta.MethodMeta", service, method.Name, method.Type.Out(0))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateService(serviceName string, svcObj any) error {
|
||||
svcType := reflect.TypeOf(svcObj)
|
||||
if svcType.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("service object %q must be a pointer", serviceName)
|
||||
}
|
||||
svcType = svcType.Elem()
|
||||
if svcType.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("service object %q must be a ptr to struct", serviceName)
|
||||
}
|
||||
for idx := 0; idx < svcType.NumMethod(); idx++ {
|
||||
method := svcType.Method(idx)
|
||||
if strings.HasSuffix(method.Name, "_Meta") {
|
||||
err := validateServiceMetaMethod(serviceName, method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := validateServiceMethod(serviceName, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateServiceMap() error {
|
||||
for svcName, svcObj := range ServiceMap {
|
||||
if err := ValidateService(svcName, svcObj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicemeta
|
||||
|
||||
type MethodMeta struct {
|
||||
Desc string
|
||||
ArgNames []string
|
||||
ReturnDesc string
|
||||
}
|
||||
Reference in New Issue
Block a user