buffer support finished

This commit is contained in:
Egor Aristov 2025-12-06 11:54:28 +03:00
parent f8d55b230d
commit cc99c892f2
Signed by: egor3f
GPG Key ID: 40482A264AAEC85F
11 changed files with 129 additions and 92 deletions

View File

@ -70,7 +70,7 @@ func main() {
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
log.Printf("call result go->ts XorData = %v", resXor) log.Printf("call result go->ts XorData = %x", resXor)
if err := ipc.Wait(1 * time.Second); err != nil { if err := ipc.Wait(1 * time.Second); err != nil {
log.Panic(err) log.Panic(err)

View File

@ -17,7 +17,7 @@ func (self *TsIpcApi) Div(
) ( ) (
int, error, int, error,
) { ) {
results, err := self.Ipc.Call("TsIpcApi.Div", a, b) results, err := self.Ipc.Call("TsIpcApi.Div", self.Ipc.Serialize(a), self.Ipc.Serialize(b))
if err != nil { if err != nil {
return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err) return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err)
} }
@ -30,7 +30,7 @@ func (self *TsIpcApi) XorData(
) ( ) (
[]byte, error, []byte, error,
) { ) {
results, err := self.Ipc.Call("TsIpcApi.XorData", data1, data2) results, err := self.Ipc.Call("TsIpcApi.XorData", self.Ipc.Serialize(data1), self.Ipc.Serialize(data2))
if err != nil { if err != nil {
return []byte{}, fmt.Errorf("call to TsIpcApi.XorData failed: %w", err) return []byte{}, fmt.Errorf("call to TsIpcApi.XorData failed: %w", err)
} }

View File

@ -1,38 +1,13 @@
package api package api
import "efprojects.com/kitten-ipc/types"
// todo check TInt size < 64 // todo check TInt size < 64
// todo check not float // todo check not float
type ValType int
const (
TInt ValType = 1
TString ValType = 2
TBool ValType = 3
TBlob ValType = 4
TArray ValType = 5
)
func (v ValType) String() string {
switch v {
case TInt:
return "int"
case TString:
return "string"
case TBool:
return "bool"
case TBlob:
return "blob"
case TArray:
return "array"
default:
panic("unreachable code")
}
}
type Val struct { type Val struct {
Name string Name string
Type ValType Type types.ValType
Children []Val Children []Val
} }

View File

@ -10,6 +10,7 @@ import (
_ "embed" _ "embed"
"efprojects.com/kitten-ipc/kitcom/internal/api" "efprojects.com/kitten-ipc/kitcom/internal/api"
"efprojects.com/kitten-ipc/types"
) )
// todo: check int overflow // todo: check int overflow
@ -40,24 +41,24 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
"receiver": func(name string) string { "receiver": func(name string) string {
return defaultReceiver return defaultReceiver
}, },
"typedef": func(t api.ValType) (string, error) { "typedef": func(t types.ValType) (string, error) {
td, ok := map[api.ValType]string{ td, ok := map[types.ValType]string{
api.TInt: "int", types.TInt: "int",
api.TString: "string", types.TString: "string",
api.TBool: "bool", types.TBool: "bool",
api.TBlob: "[]byte", types.TBlob: "[]byte",
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot generate type %v", t) return "", fmt.Errorf("cannot generate type %v", t)
} }
return td, nil return td, nil
}, },
"convtype": func(valDef string, t api.ValType) (string, error) { "convtype": func(valDef string, t types.ValType) (string, error) {
td, ok := map[api.ValType]string{ td, ok := map[types.ValType]string{
api.TInt: fmt.Sprintf("int(%s.(float64))", valDef), types.TInt: fmt.Sprintf("int(%s.(float64))", valDef),
api.TString: fmt.Sprintf("%s.(string)", valDef), types.TString: fmt.Sprintf("%s.(string)", valDef),
api.TBool: fmt.Sprintf("%s.(bool)", valDef), types.TBool: fmt.Sprintf("%s.(bool)", valDef),
api.TBlob: fmt.Sprintf( types.TBlob: fmt.Sprintf(
"%s.Ipc.ConvType(reflect.TypeOf([]byte{}), reflect.TypeOf(\"\"), %s).([]byte)", "%s.Ipc.ConvType(reflect.TypeOf([]byte{}), reflect.TypeOf(\"\"), %s).([]byte)",
defaultReceiver, defaultReceiver,
valDef, valDef,
@ -68,12 +69,12 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
} }
return td, nil return td, nil
}, },
"zerovalue": func(t api.ValType) (string, error) { "zerovalue": func(t types.ValType) (string, error) {
v, ok := map[api.ValType]string{ v, ok := map[types.ValType]string{
api.TInt: "0", types.TInt: "0",
api.TString: `""`, types.TString: `""`,
api.TBool: "false", types.TBool: "false",
api.TBlob: "[]byte{}", types.TBlob: "[]byte{}",
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot generate zero value for type %v", t) return "", fmt.Errorf("cannot generate zero value for type %v", t)

View File

@ -6,6 +6,7 @@ package {{ .PkgName }}
import ( import (
"fmt" "fmt"
"reflect"
kittenipc "efprojects.com/kitten-ipc" kittenipc "efprojects.com/kitten-ipc"
) )
@ -21,7 +22,7 @@ func ({{ $e.Name | receiver }} *{{ $e.Name }}) {{ $mtd.Name }}(
) ( ) (
{{ range $mtd.Ret }}{{ .Type | typedef }}, {{ end }}error, {{ range $mtd.Ret }}{{ .Type | typedef }}, {{ end }}error,
) { ) {
results, err := {{ $e.Name | receiver }}.Ipc.Call("{{ $e.Name }}.{{ $mtd.Name }}"{{ range $mtd.Params }}, {{ .Name }}{{ end }}) results, err := {{ $e.Name | receiver }}.Ipc.Call("{{ $e.Name }}.{{ $mtd.Name }}"{{ range $mtd.Params }}, self.Ipc.Serialize({{ .Name }}){{ end }})
if err != nil { if err != nil {
return {{ range $mtd.Ret }}{{ .Type | zerovalue }}, {{ end }} fmt.Errorf("call to {{ $e.Name }}.{{ $mtd.Name }} failed: %w", err) return {{ range $mtd.Ret }}{{ .Type | zerovalue }}, {{ end }} fmt.Errorf("call to {{ $e.Name }}.{{ $mtd.Name }} failed: %w", err)
} }

View File

@ -9,6 +9,7 @@ import (
"efprojects.com/kitten-ipc/kitcom/internal/api" "efprojects.com/kitten-ipc/kitcom/internal/api"
"efprojects.com/kitten-ipc/kitcom/internal/common" "efprojects.com/kitten-ipc/kitcom/internal/common"
"efprojects.com/kitten-ipc/types"
) )
var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`) var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`)
@ -141,11 +142,11 @@ func fieldToVal(param *ast.Field, returning bool) (*api.Val, error) {
case *ast.Ident: case *ast.Ident:
switch paramType.Name { switch paramType.Name {
case "int": case "int":
val.Type = api.TInt val.Type = types.TInt
case "string": case "string":
val.Type = api.TString val.Type = types.TString
case "bool": case "bool":
val.Type = api.TBool val.Type = types.TBool
case "error": case "error":
if returning { if returning {
return nil, nil return nil, nil
@ -160,7 +161,7 @@ func fieldToVal(param *ast.Field, returning bool) (*api.Val, error) {
case *ast.Ident: case *ast.Ident:
switch elementType.Name { switch elementType.Name {
case "byte": case "byte":
val.Type = api.TBlob val.Type = types.TBlob
default: default:
return nil, fmt.Errorf("parameter type %s is not supported yet", elementType.Name) return nil, fmt.Errorf("parameter type %s is not supported yet", elementType.Name)
} }

View File

@ -11,6 +11,7 @@ import (
_ "embed" _ "embed"
"efprojects.com/kitten-ipc/kitcom/internal/api" "efprojects.com/kitten-ipc/kitcom/internal/api"
"efprojects.com/kitten-ipc/types"
) )
//go:embed tsgen.tmpl //go:embed tsgen.tmpl
@ -30,12 +31,12 @@ func (g *TypescriptApiGenerator) Generate(apis *api.Api, destFile string) error
tpl := template.New("tsgen") tpl := template.New("tsgen")
tpl = tpl.Funcs(map[string]any{ tpl = tpl.Funcs(map[string]any{
"typedef": func(t api.ValType) (string, error) { "typedef": func(t types.ValType) (string, error) {
td, ok := map[api.ValType]string{ td, ok := map[types.ValType]string{
api.TInt: "number", types.TInt: "number",
api.TString: "string", types.TString: "string",
api.TBool: "boolean", types.TBool: "boolean",
api.TBlob: "Buffer", types.TBlob: "Buffer",
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot generate type %v", t) return "", fmt.Errorf("cannot generate type %v", t)

View File

@ -12,6 +12,7 @@ import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
"efprojects.com/kitten-ipc/types"
) )
type TypescriptApiParser struct { type TypescriptApiParser struct {
@ -108,20 +109,20 @@ func (p *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint,
return endpoints, nil return endpoints, nil
} }
func (p *TypescriptApiParser) fieldToVal(typ *ast.TypeNode) (api.ValType, error) { func (p *TypescriptApiParser) fieldToVal(typ *ast.TypeNode) (types.ValType, error) {
switch typ.Kind { switch typ.Kind {
case ast.KindNumberKeyword: case ast.KindNumberKeyword:
return api.TInt, nil return types.TInt, nil
case ast.KindStringKeyword: case ast.KindStringKeyword:
return api.TString, nil return types.TString, nil
case ast.KindBooleanKeyword: case ast.KindBooleanKeyword:
return api.TBool, nil return types.TBool, nil
case ast.KindTypeReference: case ast.KindTypeReference:
refNode := typ.AsTypeReferenceNode() refNode := typ.AsTypeReferenceNode()
ident := refNode.TypeName.AsIdentifier() ident := refNode.TypeName.AsIdentifier()
switch ident.Text { switch ident.Text {
case "Buffer": case "Buffer":
return api.TBlob, nil return types.TBlob, nil
default: default:
return 0, fmt.Errorf("reference type %s is not supported yet", ident.Text) return 0, fmt.Errorf("reference type %s is not supported yet", ident.Text)
} }

View File

@ -19,6 +19,7 @@ import (
"syscall" "syscall"
"time" "time"
"efprojects.com/kitten-ipc/types"
"github.com/samber/mo" "github.com/samber/mo"
) )
@ -48,6 +49,7 @@ type Message struct {
type IpcCommon interface { type IpcCommon interface {
Call(method string, params ...any) (Vals, error) Call(method string, params ...any) (Vals, error)
ConvType(needType reflect.Type, gotType reflect.Type, arg any) any ConvType(needType reflect.Type, gotType reflect.Type, arg any) any
Serialize(arg any) any
} }
type pendingCall struct { type pendingCall struct {
@ -160,31 +162,6 @@ func (ipc *ipcCommon) handleCall(msg Message) {
ipc.sendResponse(msg.Id, results, resErr) ipc.sendResponse(msg.Id, results, resErr)
} }
func (ipc *ipcCommon) ConvType(needType reflect.Type, gotType reflect.Type, arg any) any {
switch needType.Kind() {
case reflect.Int:
// JSON decodes any number to float64. If we need int, we should check and convert
if gotType.Kind() == reflect.Float64 {
floatArg := arg.(float64)
if float64(int64(floatArg)) == floatArg && !needType.OverflowInt(int64(floatArg)) {
arg = int(floatArg)
}
}
case reflect.Slice:
switch needType.Elem().Kind() {
case reflect.Uint8:
if gotType.Kind() == reflect.String {
var err error
arg, err = base64.StdEncoding.DecodeString(arg.(string))
if err != nil {
panic(fmt.Sprintf("decode base64: %s", err))
}
}
}
}
return arg
}
func (ipc *ipcCommon) findMethod(methodName string) (reflect.Value, error) { func (ipc *ipcCommon) findMethod(methodName string) (reflect.Value, error) {
parts := strings.Split(methodName, ".") parts := strings.Split(methodName, ".")
if len(parts) != 2 { if len(parts) != 2 {
@ -293,6 +270,46 @@ func (ipc *ipcCommon) closeConn() {
} }
} }
func (ipc *ipcCommon) ConvType(needType reflect.Type, gotType reflect.Type, arg any) any {
switch needType.Kind() {
case reflect.Int:
// JSON decodes any number to float64. If we need int, we should check and convert
if gotType.Kind() == reflect.Float64 {
floatArg := arg.(float64)
if float64(int64(floatArg)) == floatArg && !needType.OverflowInt(int64(floatArg)) {
arg = int(floatArg)
}
}
case reflect.Slice:
switch needType.Elem().Kind() {
case reflect.Uint8:
if gotType.Kind() == reflect.String {
var err error
arg, err = base64.StdEncoding.DecodeString(arg.(string))
if err != nil {
panic(fmt.Sprintf("decode base64: %s", err))
}
}
}
}
return arg
}
func (ipc *ipcCommon) Serialize(arg any) any {
t := reflect.TypeOf(arg)
switch t.Kind() {
case reflect.Slice:
switch t.Elem().Name() {
case "uint8":
return map[string]any{
"t": types.TBlob.String(),
"d": base64.StdEncoding.EncodeToString(arg.([]byte)),
}
}
}
return arg
}
type ParentIPC struct { type ParentIPC struct {
*ipcCommon *ipcCommon
cmd *exec.Cmd cmd *exec.Cmd

28
lib/golang/types/types.go Normal file
View File

@ -0,0 +1,28 @@
package types
type ValType int
const (
TInt ValType = 1
TString ValType = 2
TBool ValType = 3
TBlob ValType = 4
TArray ValType = 5
)
func (v ValType) String() string {
switch v {
case TInt:
return "int"
case TString:
return "string"
case TBool:
return "bool"
case TBlob:
return "blob"
case TArray:
return "array"
default:
panic("unreachable code")
}
}

View File

@ -142,7 +142,7 @@ abstract class IPCCommon {
try { try {
this.processingCalls++; this.processingCalls++;
let result = method.apply(endpoint, msg.args); let result = method.apply(endpoint, msg.args.map(arg => this.convType(arg)));
if (result instanceof Promise) { if (result instanceof Promise) {
result = await result; result = await result;
} }
@ -203,11 +203,23 @@ abstract class IPCCommon {
case 'boolean': case 'boolean':
case 'number': case 'number':
return arg; return arg;
// @ts-expect-error TS7029
case 'object': case 'object':
if(arg instanceof Buffer) { if(arg instanceof Buffer) {
return arg.toString('base64'); return arg.toString('base64');
} }
const keys = Object.entries(arg).map(p => p[0]).sort();
if(keys[0] === 'd' && keys[1] === 't') {
const type = arg['t'];
const data = arg['d'];
switch (type) {
case 'blob':
return Buffer.from(data, 'base64');
default:
throw new Error(`custom object type ${type} is not supported`);
}
} else {
throw new Error(`got unknown arg type: object with keys ${keys}`);
}
default: default:
throw new Error(`arg type ${typeof arg} is not supported`); throw new Error(`arg type ${typeof arg} is not supported`);
} }