port to electron (#33)

This commit is contained in:
Mike Sawka
2024-06-11 17:42:10 -07:00
committed by GitHub
parent 9f32a53485
commit 1874d9a252
62 changed files with 6498 additions and 1615 deletions
+8
View File
@@ -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 {
+94 -37
View File
@@ -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
}
+429
View File
@@ -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
}
+10
View File
@@ -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
}