-
Notifications
You must be signed in to change notification settings - Fork 246
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(go): support direct implementation of jsii interfaces
Pure go implementations of jsii interfaces are detected upon being passed as a parameter to a JavaScript call. When this happens, a `create` call is sent to the `@jsii/kernel` process with the correct list of implemented interface FQNs, and all necessary overrides records. The object is then registered into the new `ObjectStore` for later reference. When a callback request interrupts the normal request/response flow, the `kernel.Client` will handle the callback by getting the appropriate receiver object, invoking the designated go implementation, and sending the result to the `@jsii/kernel` process before resuming normal response handling.
- Loading branch information
1 parent
26882ef
commit 421d175
Showing
26 changed files
with
3,613 additions
and
1,584 deletions.
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
packages/@jsii/go-runtime/jsii-calc-test/callbacks_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
calc "github.com/aws/jsii/jsii-calc/go/jsiicalc/v3" | ||
) | ||
|
||
func TestPureInterfacesCanBeUsedTransparently(t *testing.T) { | ||
expected := calc.StructB{RequiredString: "It's Britney b**ch!"} | ||
delegate := &StructReturningDelegate{expected: expected} | ||
consumer := calc.NewConsumePureInterface(delegate) | ||
actual := consumer.WorkItBaby() | ||
|
||
if actual != expected { | ||
t.Errorf("Expected %v; actual: %v", expected, actual) | ||
} | ||
} | ||
|
||
type StructReturningDelegate struct { | ||
expected calc.StructB | ||
} | ||
|
||
func (o *StructReturningDelegate) ReturnStruct() calc.StructB { | ||
return o.expected | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 135 additions & 9 deletions
144
packages/@jsii/go-runtime/jsii-runtime-go/kernel/callbacks.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,146 @@ | ||
package kernel | ||
|
||
import "github.com/aws/jsii-runtime-go/api" | ||
import ( | ||
"fmt" | ||
"reflect" | ||
|
||
type callbacksRequest struct { | ||
kernelRequester | ||
"github.com/aws/jsii-runtime-go/api" | ||
) | ||
|
||
API string `json:"api"` | ||
type callback struct { | ||
CallbackID string `json:"cbid"` | ||
Cookie string `json:"cookie"` | ||
Invoke *invokeCallback `json:"invoke"` | ||
Get *getCallback `json:"get"` | ||
Set *setCallback `json:"set"` | ||
} | ||
|
||
type CallbacksResponse struct { | ||
kernelResponder | ||
func (c *callback) handle(result kernelResponse) error { | ||
var ( | ||
retval reflect.Value | ||
err error | ||
) | ||
if c.Invoke != nil { | ||
retval, err = c.Invoke.handle(c.Cookie) | ||
} else if c.Get != nil { | ||
retval, err = c.Get.handle(c.Cookie) | ||
} else if c.Set != nil { | ||
retval, err = c.Set.handle(c.Cookie) | ||
} else { | ||
return fmt.Errorf("invalid callback object: %v", c) | ||
} | ||
|
||
Callbacks []api.Callback `json:"callbacks"` | ||
type callbackResult struct { | ||
CallbackID string `json:"cbid"` | ||
Result interface{} `json:"result,omitempty"` | ||
Error string `json:"err,omitempty"` | ||
} | ||
type completeRequest struct { | ||
kernelRequester | ||
callbackResult `json:"complete"` | ||
} | ||
|
||
client := GetClient() | ||
request := completeRequest{} | ||
request.CallbackID = c.CallbackID | ||
request.Result = client.CastPtrToRef(retval) | ||
if err != nil { | ||
request.Error = err.Error() | ||
} | ||
return client.request(request, result) | ||
} | ||
|
||
type invokeCallback struct { | ||
Method string `json:"method"` | ||
Arguments []interface{} `json:"args"` | ||
ObjRef api.ObjectRef `json:"objref"` | ||
} | ||
|
||
func (i *invokeCallback) handle(cookie string) (retval reflect.Value, err error) { | ||
client := GetClient() | ||
|
||
receiver := reflect.ValueOf(client.GetObject(i.ObjRef)) | ||
method := receiver.MethodByName(cookie) | ||
|
||
return client.invoke(method, i.Arguments) | ||
} | ||
|
||
type getCallback struct { | ||
Property string `json:"property"` | ||
ObjRef api.ObjectRef `json:"objref"` | ||
} | ||
|
||
func (g *getCallback) handle(cookie string) (retval reflect.Value, err error) { | ||
client := GetClient() | ||
|
||
receiver := reflect.ValueOf(client.GetObject(g.ObjRef)) | ||
method := receiver.MethodByName(cookie) | ||
|
||
return client.invoke(method, nil) | ||
} | ||
|
||
type setCallback struct { | ||
Property string `json:"property"` | ||
Value interface{} `json:"value"` | ||
ObjRef api.ObjectRef `json:"objref"` | ||
} | ||
|
||
func (c *client) Callbacks() (response CallbacksResponse, err error) { | ||
err = c.request(callbacksRequest{API: "callbacks"}, &response) | ||
func (s *setCallback) handle(cookie string) (retval reflect.Value, err error) { | ||
client := GetClient() | ||
|
||
receiver := reflect.ValueOf(client.GetObject(s.ObjRef)) | ||
method := receiver.MethodByName(fmt.Sprintf("Set%v", cookie)) | ||
|
||
return client.invoke(method, []interface{}{s.Value}) | ||
} | ||
|
||
func (c *client) invoke(method reflect.Value, args []interface{}) (retval reflect.Value, err error) { | ||
if !method.IsValid() { | ||
err = fmt.Errorf("invalid method") | ||
return | ||
} | ||
|
||
// Convert the arguments, if any... | ||
callArgs := make([]reflect.Value, len(args)) | ||
methodType := method.Type() | ||
numIn := methodType.NumIn() | ||
for i, arg := range args { | ||
var argType reflect.Type | ||
if i < numIn { | ||
argType = methodType.In(i) | ||
} else if methodType.IsVariadic() { | ||
argType = methodType.In(i - 1) | ||
} else { | ||
err = fmt.Errorf("too many arguments received %d for %d", len(args), numIn) | ||
return | ||
} | ||
callArgs[i] = reflect.New(argType) | ||
c.CastAndSetToPtr(arg, callArgs[i].Interface()) | ||
} | ||
|
||
// Ready to catch an error if the method panics... | ||
defer func() { | ||
if r := recover(); r != nil { | ||
if err == nil { | ||
var ok bool | ||
if err, ok = r.(error); !ok { | ||
err = fmt.Errorf("%v", r) | ||
} | ||
} else { | ||
// This is not expected - so we panic! | ||
panic(r) | ||
} | ||
} | ||
}() | ||
|
||
result := method.Call(callArgs) | ||
switch len(result) { | ||
case 0: | ||
retval = reflect.ValueOf(nil) | ||
case 1: | ||
retval = result[0] | ||
default: | ||
err = fmt.Errorf("too many return values: %v", result) | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.