├── version ├── internal ├── embedded │ ├── .gitattributes │ ├── doc.go │ ├── embedded_test.go │ └── embedded.go ├── kernel │ ├── version.generated.go │ ├── doc.go │ ├── stats.go │ ├── end.go │ ├── del.go │ ├── naming.go │ ├── complete.go │ ├── process │ │ ├── handshake.go │ │ ├── jsii-mock-runtime.js │ │ ├── consume-stderr.go │ │ ├── process_test.go │ │ └── process.go │ ├── begin.go │ ├── create.go │ ├── get.go │ ├── set.go │ ├── invoke.go │ ├── json.go │ ├── request-response-markers.go │ ├── client_test.go │ ├── load.go │ ├── manage-object.go │ ├── client.go │ ├── callbacks.go │ └── conversions.go ├── api │ ├── doc.go │ └── api.go ├── typeregistry │ ├── doc.go │ ├── overrides.go │ ├── validate-struct.go │ ├── discover-implemented.go │ ├── typeregistry.go │ └── registration.go ├── compiler │ ├── go1.16.go │ └── go1.17.go └── objectstore │ ├── doc.go │ └── objectstore.go ├── NOTICE ├── doc.go ├── runtime ├── doc.go ├── type_checking.go ├── overrides_reflect_test.go └── runtime.go ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── jsii.go ├── tools.go ├── README.md ├── go.mod ├── deprecationwarning.go ├── cast.go ├── cast_test.go ├── helpers.go ├── helpers_test.go ├── go.sum ├── deprecated.go └── LICENSE /version: -------------------------------------------------------------------------------- 1 | 1.121.0 2 | -------------------------------------------------------------------------------- /internal/embedded/.gitattributes: -------------------------------------------------------------------------------- 1 | resources/ linguist-vendored 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | jsii 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /internal/kernel/version.generated.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | const version = "1.121.0" 4 | -------------------------------------------------------------------------------- /internal/embedded/doc.go: -------------------------------------------------------------------------------- 1 | // Package embedded contains the embedded @jsii/kernel code, which is used 2 | // unless the JSII_RUNTIME environment variable is set to a different program. 3 | package embedded 4 | -------------------------------------------------------------------------------- /internal/api/doc.go: -------------------------------------------------------------------------------- 1 | // Package api contains shared type definisions used by several modules part of 2 | // the jsii runtime for go. It helps avoid creating circular dependencies 3 | // between the other modules. 4 | package api 5 | -------------------------------------------------------------------------------- /internal/kernel/doc.go: -------------------------------------------------------------------------------- 1 | // Package kernel defines the interface for go programs to interact with the 2 | // @jsii/kernel node process. This module completely abstracts managament of the 3 | // child process. 4 | package kernel 5 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package jsii provides utilities that user code can leverage to facilitate 2 | // working with libraries generated by the `jsii-pacmak` tool. This includes 3 | // type conversion helpers as well as utilities to manage the runtime process' 4 | // lifecycle. 5 | package jsii 6 | -------------------------------------------------------------------------------- /runtime/doc.go: -------------------------------------------------------------------------------- 1 | // Package runtime provides the APIs used by code generated by the `jsii-pacmak` 2 | // tool to interact with the `@jsii/kernel` process. It is not intended for 3 | // users to directly interact with, and doing so could result in incorrect 4 | // behavior. 5 | package runtime 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | The jsii runtime library for go's source code is managed in the 2 | [main jsii repository][jsii]. See the [contributing] guide for details on filing 3 | issues and PRs. 4 | 5 | [jsii]: https://github.com/aws/jsii 6 | [contributing]: https://github.com/aws/jsii/blob/main/CONTRIBUTING.md 7 | -------------------------------------------------------------------------------- /internal/kernel/stats.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | type StatsResponse struct { 4 | kernelResponse 5 | ObjectCount float64 `json:"object_count"` 6 | } 7 | 8 | func (c *Client) Stats() (response StatsResponse, err error) { 9 | err = c.request(kernelRequest{"stats"}, &response) 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /internal/typeregistry/doc.go: -------------------------------------------------------------------------------- 1 | // Package typeregistry offers features useful to manage the registration and 2 | // operation of types in the context of the jsii runtime. These are used to 3 | // support type conversion for values exchanged between the child node process 4 | // and the go application. 5 | package typeregistry 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /jsii.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/kernel" 4 | 5 | // Close finalizes the runtime process, signalling the end of the execution to 6 | // the jsii kernel process, and waiting for graceful termination. The best 7 | // practice is to defer call this at the beginning of the "main" function. 8 | func Close() { 9 | kernel.CloseClient() 10 | } 11 | -------------------------------------------------------------------------------- /internal/embedded/embedded_test.go: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import ( 4 | "path" 5 | "testing" 6 | ) 7 | 8 | func TestEntryPointExists(t *testing.T) { 9 | if bytes, err := embeddedFS.ReadFile(path.Join(embeddedRootDir, entrypointName)); err != nil { 10 | t.Errorf("unable to read entry point data: %v", err) 11 | } else if len(bytes) == 0 { 12 | t.Error("entry point file is empty") 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /internal/typeregistry/overrides.go: -------------------------------------------------------------------------------- 1 | package typeregistry 2 | 3 | import ( 4 | "github.com/aws/jsii-runtime-go/internal/api" 5 | ) 6 | 7 | func (t *TypeRegistry) GetOverride(fqn api.FQN, n string) (api.Override, bool) { 8 | if members, ok := t.typeMembers[fqn]; ok { 9 | for _, member := range members { 10 | if member.GoName() == n { 11 | return member, true 12 | } 13 | } 14 | } 15 | 16 | return nil, false 17 | } 18 | -------------------------------------------------------------------------------- /internal/compiler/go1.16.go: -------------------------------------------------------------------------------- 1 | //go:build go1.16 && !go1.17 2 | // +build go1.16,!go1.17 3 | 4 | package compiler 5 | 6 | // Version denotes the version of the go compiler that was used for building 7 | // this binary. It is intended for use only in the compiler deprecation warning 8 | // message. 9 | const Version = "go1.16" 10 | 11 | // EndOfLifeDate is the date at which this compiler version reached end-of-life. 12 | const EndOfLifeDate = "2022-03-15" 13 | -------------------------------------------------------------------------------- /internal/compiler/go1.17.go: -------------------------------------------------------------------------------- 1 | //go:build go1.17 && !go1.18 2 | // +build go1.17,!go1.18 3 | 4 | package compiler 5 | 6 | // Version denotes the version of the go compiler that was used for building 7 | // this binary. It is intended for use only in the compiler deprecation warning 8 | // message. 9 | const Version = "go1.17" 10 | 11 | // EndOfLifeDate is the date at which this compiler version reached end-of-life. 12 | const EndOfLifeDate = "2022-08-02" 13 | -------------------------------------------------------------------------------- /internal/kernel/end.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | type EndProps struct { 4 | PromiseID *string `json:"promise_id"` 5 | } 6 | 7 | type EndResponse struct { 8 | kernelResponse 9 | Result interface{} `json:"result"` 10 | } 11 | 12 | func (c *Client) End(props EndProps) (response EndResponse, err error) { 13 | type request struct { 14 | kernelRequest 15 | EndProps 16 | } 17 | err = c.request(request{kernelRequest{"end"}, props}, &response) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /internal/objectstore/doc.go: -------------------------------------------------------------------------------- 1 | // Package objectstore implements support for tracking a mapping of object 2 | // references to and from their instance ID. It tracks objects by proxy of their 3 | // memory address (i.e: pointer value), in order to avoid the pitfalls of go's 4 | // standard object equality mechanism (which is also reflect.Value's equality 5 | // mechanism) causing distinct instances appearing to be equal (including when 6 | // used as keys to a map). 7 | package objectstore 8 | -------------------------------------------------------------------------------- /runtime/type_checking.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/kernel" 4 | 5 | // ValidateStruct runs validations on the supplied struct to determine whether 6 | // it is valid. In particular, it checks union-typed properties to ensure the 7 | // provided value is of one of the allowed types. 8 | func ValidateStruct(v interface{}, d func() string) error { 9 | client := kernel.GetClient() 10 | return client.Types().ValidateStruct(v, d) 11 | } 12 | -------------------------------------------------------------------------------- /internal/kernel/del.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/api" 4 | 5 | type DelProps struct { 6 | ObjRef api.ObjectRef `json:"objref"` 7 | } 8 | 9 | type DelResponse struct { 10 | kernelResponse 11 | } 12 | 13 | func (c *Client) Del(props DelProps) (response DelResponse, err error) { 14 | type request struct { 15 | kernelRequest 16 | DelProps 17 | } 18 | err = c.request(request{kernelRequest{"del"}, props}, &response) 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // Package tools contains the necessary statements to ensure tool dependencies 5 | // are not "cleaned up" by "go mod tidy" despite being used... The "// +" 6 | // comment above makes sure the file is never included in an actual compiled 7 | // unit (unless someone manually specifies the "build tools" tag at compile 8 | // time). 9 | package tools 10 | 11 | import ( 12 | _ "golang.org/x/lint/golint" 13 | _ "golang.org/x/tools/cmd/godoc" 14 | _ "golang.org/x/tools/cmd/goimports" 15 | ) 16 | -------------------------------------------------------------------------------- /internal/kernel/naming.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | type NamingProps struct { 4 | Assembly string `json:"assembly"` 5 | } 6 | 7 | type NamingResponse struct { 8 | kernelResponse 9 | // readonly naming: { 10 | // readonly [language: string]: { readonly [key: string]: any } | undefined; 11 | // }; 12 | } 13 | 14 | func (c *Client) Naming(props NamingProps) (response NamingResponse, err error) { 15 | type request struct { 16 | kernelRequest 17 | NamingProps 18 | } 19 | err = c.request(request{kernelRequest{"naming"}, props}, &response) 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /internal/kernel/complete.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | type CompleteProps struct { 4 | CallbackID *string `json:"cbid"` 5 | Error *string `json:"err"` 6 | Result interface{} `json:"result"` 7 | } 8 | 9 | type CompleteResponse struct { 10 | kernelResponse 11 | CallbackID *string `json:"cbid"` 12 | } 13 | 14 | func (c *Client) Complete(props CompleteProps) (response CompleteResponse, err error) { 15 | type request struct { 16 | kernelRequest 17 | CompleteProps 18 | } 19 | err = c.request(request{kernelRequest{"complete"}, props}, &response) 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /internal/kernel/process/handshake.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/Masterminds/semver/v3" 8 | ) 9 | 10 | type handshakeResponse struct { 11 | Hello string `json:"hello"` 12 | } 13 | 14 | func (h *handshakeResponse) runtimeVersion() (*semver.Version, error) { 15 | re := regexp.MustCompile("@") 16 | parts := re.Split(h.Hello, 3) 17 | switch len(parts) { 18 | case 2: 19 | return semver.NewVersion(parts[1]) 20 | case 3: 21 | return semver.NewVersion(parts[2]) 22 | default: 23 | return nil, fmt.Errorf("invalid handshake payload: %v", h.Hello) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/kernel/begin.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "github.com/aws/jsii-runtime-go/internal/api" 5 | ) 6 | 7 | type BeginProps struct { 8 | Method *string `json:"method"` 9 | Arguments []interface{} `json:"args"` 10 | ObjRef api.ObjectRef `json:"objref"` 11 | } 12 | 13 | type BeginResponse struct { 14 | kernelResponse 15 | PromiseID *string `json:"promise_id"` 16 | } 17 | 18 | func (c *Client) Begin(props BeginProps) (response BeginResponse, err error) { 19 | type request struct { 20 | kernelRequest 21 | BeginProps 22 | } 23 | err = c.request(request{kernelRequest{"begin"}, props}, &response) 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![jsii](https://github.com/aws/jsii/raw/main/logo/png/128.png) 2 | 3 | ## The jsii runtime library for Go 4 | 5 | This is the [jsii] runtime for go. This repository is used only for publishing 6 | the go module. The source code is managed in [the main JSII repository][jsii]. 7 | Refer to the [go-runtime readme there][readme] for details on building and 8 | testing the module. 9 | 10 | [jsii]: https://github.com/aws/jsii 11 | [readme]: https://github.com/aws/jsii/blob/main/packages/%40jsii/go-runtime/README.md 12 | 13 | ## :balance_scale: License 14 | 15 | **jsii** is distributed under the [Apache License, Version 2.0][apache-2.0]. 16 | 17 | See [LICENSE](./LICENSE) and [NOTICE](./NOTICE) for more information. 18 | 19 | [apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws/jsii-runtime-go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.5 6 | 7 | require ( 8 | github.com/Masterminds/semver/v3 v3.4.0 9 | github.com/fatih/color v1.18.0 10 | github.com/mattn/go-isatty v0.0.20 11 | github.com/stretchr/testify v1.11.1 12 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 13 | golang.org/x/tools v0.36.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/mattn/go-colorable v0.1.13 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/yuin/goldmark v1.4.13 // indirect 21 | golang.org/x/mod v0.27.0 // indirect 22 | golang.org/x/sync v0.16.0 // indirect 23 | golang.org/x/sys v0.35.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | 27 | retract v1.27.0 28 | -------------------------------------------------------------------------------- /internal/typeregistry/validate-struct.go: -------------------------------------------------------------------------------- 1 | package typeregistry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ValidateStruct runs validations on the supplied struct to determine whether 9 | // it is valid. In particular, it checks union-typed properties to ensure the 10 | // provided value is of one of the allowed types. 11 | // 12 | // May panic if v is not a pointer to a struct value. 13 | func (t *TypeRegistry) ValidateStruct(v interface{}, d func() string) error { 14 | rt := reflect.TypeOf(v).Elem() 15 | 16 | info, ok := t.structInfo[rt] 17 | if !ok { 18 | return fmt.Errorf("%v: %v is not a know struct type", d(), rt) 19 | } 20 | 21 | // There may not be a validator (type is simple enough, etc...). 22 | if info.Validator != nil { 23 | return info.Validator(v, d) 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/kernel/create.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/api" 4 | 5 | type CreateProps struct { 6 | FQN api.FQN `json:"fqn"` 7 | Interfaces []api.FQN `json:"interfaces,omitempty"` 8 | Arguments []interface{} `json:"args,omitempty"` 9 | Overrides []api.Override `json:"overrides,omitempty"` 10 | } 11 | 12 | // TODO extends AnnotatedObjRef? 13 | type CreateResponse struct { 14 | kernelResponse 15 | InstanceID string `json:"$jsii.byref"` 16 | } 17 | 18 | func (c *Client) Create(props CreateProps) (response CreateResponse, err error) { 19 | type request struct { 20 | kernelRequest 21 | CreateProps 22 | } 23 | err = c.request(request{kernelRequest{"create"}, props}, &response) 24 | return 25 | } 26 | 27 | // UnmarshalJSON provides custom unmarshalling implementation for response 28 | // structs. Creating new types is required in order to avoid infinite recursion. 29 | func (r *CreateResponse) UnmarshalJSON(data []byte) error { 30 | type response CreateResponse 31 | return unmarshalKernelResponse(data, (*response)(r), r) 32 | } 33 | -------------------------------------------------------------------------------- /internal/kernel/process/jsii-mock-runtime.js: -------------------------------------------------------------------------------- 1 | const process = require('process'); 2 | 3 | /** 4 | * Executes a pretend jsii runtime process that advertises the 5 | * provided runtime version number. 6 | * 7 | * It response to any request except for "exit" by repeating it 8 | * back to the parent process. The "exit" handling is "standard". 9 | * 10 | * @param version the version number to report in the HELLO message. 11 | */ 12 | function main(version) { 13 | console.log(JSON.stringify({ hello: `@mock/jsii-runtime@${version}` })); 14 | 15 | let buffer = ""; 16 | process.stdin.setEncoding('utf8'); 17 | process.stdin.on('data', chunk => { 18 | buffer = buffer + chunk; 19 | nl = buffer.indexOf('\n'); 20 | if (nl >= 0) { 21 | const line = buffer.substring(0, nl + 1); 22 | buffer = buffer.substring(nl + 1); 23 | 24 | const message = JSON.parse(line); 25 | if (message.exit) { 26 | process.exit(message.exit); 27 | } else { 28 | console.log(JSON.stringify(message)); 29 | } 30 | } 31 | }); 32 | } 33 | 34 | main(...process.argv.slice(2)); -------------------------------------------------------------------------------- /internal/kernel/get.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/api" 4 | 5 | type GetProps struct { 6 | Property string `json:"property"` 7 | ObjRef api.ObjectRef `json:"objref"` 8 | } 9 | 10 | type StaticGetProps struct { 11 | FQN api.FQN `json:"fqn"` 12 | Property string `json:"property"` 13 | } 14 | 15 | type GetResponse struct { 16 | kernelResponse 17 | Value interface{} `json:"value"` 18 | } 19 | 20 | func (c *Client) Get(props GetProps) (response GetResponse, err error) { 21 | type request struct { 22 | kernelRequest 23 | GetProps 24 | } 25 | err = c.request(request{kernelRequest{"get"}, props}, &response) 26 | return 27 | } 28 | 29 | func (c *Client) SGet(props StaticGetProps) (response GetResponse, err error) { 30 | type request struct { 31 | kernelRequest 32 | StaticGetProps 33 | } 34 | err = c.request(request{kernelRequest{"sget"}, props}, &response) 35 | return 36 | } 37 | 38 | // UnmarshalJSON provides custom unmarshalling implementation for response 39 | // structs. Creating new types is required in order to avoid infinite recursion. 40 | func (r *GetResponse) UnmarshalJSON(data []byte) error { 41 | type response GetResponse 42 | return unmarshalKernelResponse(data, (*response)(r), r) 43 | } 44 | -------------------------------------------------------------------------------- /internal/kernel/process/consume-stderr.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | ) 9 | 10 | type consoleMessage struct { 11 | Stderr []byte `json:"stderr"` 12 | Stdout []byte `json:"stdout"` 13 | } 14 | 15 | // consumeStderr is intended to be used as a goroutine, and will consume this 16 | // process' stderr stream until it reaches EOF. It reads the stream line-by-line 17 | // and will decode any console messages per the jsii wire protocol specification. 18 | // Once EOF has been reached, true will be sent to the done channel, allowing 19 | // other goroutines to check whether the goroutine has reached EOF (and hence 20 | // finished) or not. 21 | func (p *Process) consumeStderr(done chan bool) { 22 | reader := bufio.NewReader(p.stderr) 23 | 24 | for true { 25 | line, err := reader.ReadBytes('\n') 26 | if len(line) == 0 || err == io.EOF { 27 | done <- true 28 | return 29 | } 30 | var message consoleMessage 31 | if err := json.Unmarshal(line, &message); err != nil { 32 | os.Stderr.Write(line) 33 | } else { 34 | if message.Stderr != nil { 35 | os.Stderr.Write(message.Stderr) 36 | } 37 | if message.Stdout != nil { 38 | os.Stdout.Write(message.Stdout) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/kernel/set.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/api" 4 | 5 | type SetProps struct { 6 | Property string `json:"property"` 7 | Value interface{} `json:"value"` 8 | ObjRef api.ObjectRef `json:"objref"` 9 | } 10 | 11 | type StaticSetProps struct { 12 | FQN api.FQN `json:"fqn"` 13 | Property string `json:"property"` 14 | Value interface{} `json:"value"` 15 | } 16 | 17 | type SetResponse struct { 18 | kernelResponse 19 | } 20 | 21 | func (c *Client) Set(props SetProps) (response SetResponse, err error) { 22 | type request struct { 23 | kernelRequest 24 | SetProps 25 | } 26 | err = c.request(request{kernelRequest{"set"}, props}, &response) 27 | return 28 | } 29 | 30 | func (c *Client) SSet(props StaticSetProps) (response SetResponse, err error) { 31 | type request struct { 32 | kernelRequest 33 | StaticSetProps 34 | } 35 | err = c.request(request{kernelRequest{"sset"}, props}, &response) 36 | return 37 | } 38 | 39 | // UnmarshalJSON provides custom unmarshalling implementation for response 40 | // structs. Creating new types is required in order to avoid infinite recursion. 41 | func (r *SetResponse) UnmarshalJSON(data []byte) error { 42 | type response SetResponse 43 | return unmarshalKernelResponse(data, (*response)(r), r) 44 | } 45 | -------------------------------------------------------------------------------- /runtime/overrides_reflect_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | ) 8 | 9 | type IFace interface { 10 | M1() 11 | M2() 12 | M3() 13 | } 14 | 15 | type Base struct { 16 | X, Y int 17 | } 18 | 19 | func (b *Base) M1() {} 20 | func (b *Base) M2() {} 21 | func (b *Base) M3() {} 22 | 23 | type D1 struct { 24 | *Base 25 | } 26 | 27 | func (d *D1) M1() {} 28 | 29 | func (d *D1) X1() {} 30 | 31 | type D2 struct { 32 | Name string 33 | IFace 34 | } 35 | 36 | func (d *D2) M2() {} 37 | 38 | func (d *D2) X2() {} 39 | 40 | func TestOverrideReflection(t *testing.T) { 41 | testCases := [...]struct { 42 | //overriding instance 43 | val interface{} 44 | //instance overriding methods 45 | methods []string 46 | }{ 47 | {&Base{}, []string(nil)}, 48 | {&D1{&Base{}}, []string{"M1", "X1"}}, 49 | {&D2{Name: "abc", IFace: &D1{&Base{}}}, []string{"M1", "X1", "M2", "X2"}}, 50 | } 51 | for _, tc := range testCases { 52 | sort.Slice(tc.methods, func(i, j int) bool { 53 | return tc.methods[i] < tc.methods[j] 54 | }) 55 | } 56 | for _, tc := range testCases { 57 | methods := getMethodOverrides(tc.val, "Base") 58 | sort.Slice(methods, func(i, j int) bool { 59 | return methods[i] < methods[j] 60 | }) 61 | if !reflect.DeepEqual(methods, tc.methods) { 62 | t.Errorf("expect: %v, got: %v", tc.methods, methods) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/kernel/invoke.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import "github.com/aws/jsii-runtime-go/internal/api" 4 | 5 | type InvokeProps struct { 6 | Method string `json:"method"` 7 | Arguments []interface{} `json:"args"` 8 | ObjRef api.ObjectRef `json:"objref"` 9 | } 10 | 11 | type StaticInvokeProps struct { 12 | FQN api.FQN `json:"fqn"` 13 | Method string `json:"method"` 14 | Arguments []interface{} `json:"args"` 15 | } 16 | 17 | type InvokeResponse struct { 18 | kernelResponse 19 | Result interface{} `json:"result"` 20 | } 21 | 22 | func (c *Client) Invoke(props InvokeProps) (response InvokeResponse, err error) { 23 | type request struct { 24 | kernelRequest 25 | InvokeProps 26 | } 27 | err = c.request(request{kernelRequest{"invoke"}, props}, &response) 28 | return 29 | } 30 | 31 | func (c *Client) SInvoke(props StaticInvokeProps) (response InvokeResponse, err error) { 32 | type request struct { 33 | kernelRequest 34 | StaticInvokeProps 35 | } 36 | err = c.request(request{kernelRequest{"sinvoke"}, props}, &response) 37 | return 38 | } 39 | 40 | // UnmarshalJSON provides custom unmarshalling implementation for response 41 | // structs. Creating new types is required in order to avoid infinite recursion. 42 | func (r *InvokeResponse) UnmarshalJSON(data []byte) error { 43 | type response InvokeResponse 44 | return unmarshalKernelResponse(data, (*response)(r), r) 45 | } 46 | -------------------------------------------------------------------------------- /deprecationwarning.go: -------------------------------------------------------------------------------- 1 | //go:build (go1.16 || go1.17) && !go1.18 2 | // +build go1.16 go1.17 3 | // +build !go1.18 4 | 5 | package jsii 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/aws/jsii-runtime-go/internal/compiler" 12 | "github.com/fatih/color" 13 | "github.com/mattn/go-isatty" 14 | ) 15 | 16 | // / Emits a deprecation warning message when 17 | func init() { 18 | tty := isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd()) 19 | 20 | if tty { 21 | // Set terminal to bold red 22 | color.Set(color.FgRed, color.Bold) 23 | } 24 | 25 | fmt.Fprintln(os.Stderr, "###########################################################") 26 | fmt.Fprintf(os.Stderr, "# This binary was compiled with %v, which has reached #\n", compiler.Version) 27 | fmt.Fprintf(os.Stderr, "# end-of-life on %v. #\n", compiler.EndOfLifeDate) 28 | fmt.Fprintln(os.Stderr, "# #") 29 | fmt.Fprintln(os.Stderr, "# Support for this version WILL be dropped in the future! #") 30 | fmt.Fprintln(os.Stderr, "# #") 31 | fmt.Fprintln(os.Stderr, "# See https://go.dev/security for more information. #") 32 | fmt.Fprintln(os.Stderr, "###########################################################") 33 | 34 | if tty { 35 | // Reset terminal back to normal 36 | color.Unset() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/kernel/json.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // unmarshalKernelResponse performs custom unmarshaling for kernel responses, checks for presence of `error` key on json 9 | // and returns if present. 10 | // 11 | // The uresult parameter may be passed to json.Unmarshal so if unmarshalKernelResponse is called within a custom 12 | // UnmarshalJSON function, it must be a type alias (otherwise, this will recurse into itself until the stack is full). 13 | // The result parameter must point to the original result (with the original type). This unfortunate duplication is 14 | // necessary for the proper handling of in-line callbacks. 15 | func unmarshalKernelResponse(data []byte, uresult kernelResponder, result kernelResponder) error { 16 | datacopy := make([]byte, len(data)) 17 | copy(datacopy, data) 18 | 19 | var response map[string]json.RawMessage 20 | if err := json.Unmarshal(datacopy, &response); err != nil { 21 | return err 22 | } 23 | 24 | if err, ok := response["error"]; ok { 25 | return errors.New(string(err)) 26 | } 27 | 28 | // In-line callback requests interrupt the current flow, the callback handling 29 | // logic will resume this handling once the callback request has been fulfilled. 30 | if raw, ok := response["callback"]; ok { 31 | callback := callback{} 32 | if err := json.Unmarshal(raw, &callback); err != nil { 33 | return err 34 | } 35 | 36 | return callback.handle(result) 37 | } 38 | 39 | return json.Unmarshal(response["ok"], uresult) 40 | } 41 | -------------------------------------------------------------------------------- /internal/kernel/request-response-markers.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | // kernelRequester allows creating a union of kernelRequester and kernelResponder 4 | // types by defining private method implemented by a private custom type, which 5 | // is embedded in all relevant types. 6 | type kernelRequester interface { 7 | // isRequest is a marker method that is intended to ensure no external type 8 | // can implement this interface. 9 | isRequest() kernelBrand 10 | } 11 | 12 | // kernelRequest is the standard implementation for kernelRequester. 13 | type kernelRequest struct { 14 | API string `json:"api"` 15 | } 16 | 17 | func (r kernelRequest) isRequest() kernelBrand { 18 | return kernelBrand{} 19 | } 20 | 21 | // kernelResponder allows creating a union of kernelResponder and kernelRequester 22 | // types by defining private method implemented by a private custom type, which 23 | // is embedded in all relevant types. 24 | type kernelResponder interface { 25 | // isResponse is a marker method that is intended to ensure no external type 26 | // can implement this interface. 27 | isResponse() kernelBrand 28 | } 29 | 30 | // kernelResponse is a 0-width marker struc tembedded to make another type be 31 | // usable as a kernelResponder. 32 | type kernelResponse struct{} 33 | 34 | func (r kernelResponse) isResponse() kernelBrand { 35 | return kernelBrand{} 36 | } 37 | 38 | // kernelBrand is a private type used to ensure the kernelRequester and 39 | // kernelResponder cannot be implemented outside of this package. 40 | type kernelBrand struct{} 41 | -------------------------------------------------------------------------------- /internal/kernel/client_test.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/typeregistry" 8 | ) 9 | 10 | func TestClient(t *testing.T) { 11 | client, err := newClient() 12 | if err != nil { 13 | t.Fatalf("client init failed: %v", err.Error()) 14 | } 15 | defer client.close() 16 | 17 | t.Run("Client Load Error", func(t *testing.T) { 18 | request := LoadProps{ 19 | Name: "jsii-calc", 20 | Version: "0.0.0", 21 | } 22 | 23 | res, err := client.Load(request, nil) 24 | 25 | t.Log(res) 26 | if err != nil { 27 | t.Log(err) 28 | } 29 | }) 30 | 31 | t.Run("Type registry survives CloseClient()", func(t *testing.T) { 32 | client, err := newClient() 33 | if err != nil { 34 | t.Fatalf("client init failed: %v", err.Error()) 35 | } 36 | 37 | // Clean up after ourselves, so this test does not leave traces behind. 38 | defer func() { types = typeregistry.New() }() 39 | 40 | type enumType string 41 | var enumTypeFOO enumType = "FOO" 42 | 43 | registry := client.Types() 44 | err = registry.RegisterEnum( 45 | "example.Enum", 46 | reflect.TypeOf((*enumType)(nil)).Elem(), 47 | map[string]interface{}{"FOO": enumTypeFOO}, 48 | ) 49 | if err != nil { 50 | t.Fatalf("failed registering enum: %v", err.Error()) 51 | } 52 | 53 | CloseClient() 54 | 55 | // Getting the registry from the client again. 56 | registry = client.Types() 57 | 58 | if _, ok := registry.TryRenderEnumRef(reflect.ValueOf(enumTypeFOO)); !ok { 59 | t.Errorf("failed rendering enum ref, it should have worked!") 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /internal/embedded/embedded.go: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import ( 4 | "embed" 5 | "os" 6 | "path" 7 | ) 8 | 9 | // embeddedRootDir is the name of the root directory for the embeddedFS variable. 10 | const embeddedRootDir string = "resources" 11 | 12 | //go:embed resources/* 13 | var embeddedFS embed.FS 14 | 15 | // entrypointName is the path to the entry point relative to the embeddedRootDir. 16 | var entrypointName = path.Join("bin", "jsii-runtime.js") 17 | 18 | // ExtractRuntime extracts a copy of the embedded runtime library into 19 | // the designated directory, and returns the fully qualified path to the entry 20 | // point to be used when starting the child process. 21 | func ExtractRuntime(into string) (entrypoint string, err error) { 22 | err = extractRuntime(into, embeddedRootDir) 23 | if err == nil { 24 | entrypoint = path.Join(into, entrypointName) 25 | } 26 | return 27 | } 28 | 29 | // extractRuntime copies the contents of embeddedFS at "from" to the provided 30 | // "into" directory, recursively. 31 | func extractRuntime(into string, from string) error { 32 | files, err := embeddedFS.ReadDir(from) 33 | if err != nil { 34 | return err 35 | } 36 | for _, file := range files { 37 | src := path.Join(from, file.Name()) 38 | dest := path.Join(into, file.Name()) 39 | if file.IsDir() { 40 | if err = os.Mkdir(dest, 0o700); err != nil { 41 | return err 42 | } 43 | if err = extractRuntime(dest, src); err != nil { 44 | return err 45 | } 46 | } else { 47 | data, err := embeddedFS.ReadFile(src) 48 | if err != nil { 49 | return err 50 | } 51 | if err = os.WriteFile(dest, data, 0o600); err != nil { 52 | return err 53 | } 54 | } 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /internal/kernel/load.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "regexp" 8 | ) 9 | 10 | // LoadProps holds the necessary information to load a library into the 11 | // @jsii/kernel process through the Load method. 12 | type LoadProps struct { 13 | Name string `json:"name"` 14 | Version string `json:"version"` 15 | } 16 | 17 | // LoadResponse contains the data returned by the @jsii/kernel process in 18 | // response to a load request. 19 | type LoadResponse struct { 20 | kernelResponse 21 | Assembly string `json:"assembly"` 22 | Types float64 `json:"types"` 23 | } 24 | 25 | // Load ensures the specified assembly has been loaded into the @jsii/kernel 26 | // process. This call is idempotent (calling it several times with the same 27 | // input results in the same output). 28 | func (c *Client) Load(props LoadProps, tarball []byte) (response LoadResponse, err error) { 29 | if response, cached := c.loaded[props]; cached { 30 | return response, nil 31 | } 32 | 33 | tmpfile, err := ioutil.TempFile("", fmt.Sprintf( 34 | "%v-%v.*.tgz", 35 | regexp.MustCompile("[^a-zA-Z0-9_-]").ReplaceAllString(props.Name, "-"), 36 | version, 37 | )) 38 | if err != nil { 39 | return 40 | } 41 | defer os.Remove(tmpfile.Name()) 42 | if _, err := tmpfile.Write(tarball); err != nil { 43 | panic(err) 44 | } 45 | tmpfile.Close() 46 | 47 | type request struct { 48 | kernelRequest 49 | LoadProps 50 | Tarball string `json:"tarball"` 51 | } 52 | err = c.request(request{kernelRequest{"load"}, props, tmpfile.Name()}, &response) 53 | 54 | if err == nil { 55 | c.loaded[props] = response 56 | } 57 | 58 | return 59 | } 60 | 61 | // UnmarshalJSON provides custom unmarshalling implementation for response 62 | // structs. Creating new types is required in order to avoid infinite recursion. 63 | func (r *LoadResponse) UnmarshalJSON(data []byte) error { 64 | type response LoadResponse 65 | return unmarshalKernelResponse(data, (*response)(r), r) 66 | } 67 | -------------------------------------------------------------------------------- /internal/typeregistry/discover-implemented.go: -------------------------------------------------------------------------------- 1 | package typeregistry 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/api" 8 | ) 9 | 10 | // DiscoverImplementation determines the list of registered interfaces that are 11 | // implemented by the provided type, and returns the list of their FQNs and 12 | // overrides for all their combined methods and properties. 13 | func (t *TypeRegistry) DiscoverImplementation(vt reflect.Type) (interfaces []api.FQN, overrides []api.Override) { 14 | if strings.HasPrefix(vt.Name(), "jsiiProxy_") { 15 | return 16 | } 17 | 18 | registeredOverrides := make(map[string]bool) 19 | embeds := t.registeredBasesOf(vt) 20 | 21 | pt := reflect.PtrTo(vt) 22 | 23 | OuterLoop: 24 | for fqn, members := range t.typeMembers { 25 | iface := t.fqnToType[fqn] 26 | if iface.Kind == classType || !(vt.AssignableTo(iface.Type) || pt.AssignableTo(iface.Type)) { 27 | continue 28 | } 29 | for _, embed := range embeds { 30 | if embed.AssignableTo(iface.Type) { 31 | continue OuterLoop 32 | } 33 | } 34 | // Found a hit, registering it's FQN in the list! 35 | interfaces = append(interfaces, fqn) 36 | 37 | // Now, collecting all members thereof 38 | for _, override := range members { 39 | if identifier := override.GoName(); !registeredOverrides[identifier] { 40 | registeredOverrides[identifier] = true 41 | overrides = append(overrides, override) 42 | } 43 | } 44 | } 45 | 46 | return 47 | } 48 | 49 | // registeredBasesOf looks for known base type anonymously embedded (not 50 | // recursively) in the given value type. Interfaces implemented by those types 51 | // are actually not "additional interfaces" (they are implied). 52 | func (t *TypeRegistry) registeredBasesOf(vt reflect.Type) []reflect.Type { 53 | if vt.Kind() == reflect.Ptr { 54 | vt = vt.Elem() 55 | } 56 | if vt.Kind() != reflect.Struct { 57 | return nil 58 | } 59 | n := vt.NumField() 60 | result := make([]reflect.Type, 0, n) 61 | for i := 0; i < n; i++ { 62 | f := vt.Field(i) 63 | if !f.Anonymous { 64 | continue 65 | } 66 | if _, ok := t.proxyMakers[f.Type]; ok { 67 | result = append(result, f.Type) 68 | } 69 | } 70 | return result 71 | } 72 | -------------------------------------------------------------------------------- /cast.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/kernel" 8 | ) 9 | 10 | // UnsafeCast converts the given interface value to the desired target interface 11 | // pointer. Panics if the from value is not a jsii proxy object, or if the to 12 | // value is not a pointer to an interface type. 13 | func UnsafeCast(from interface{}, into interface{}) { 14 | rinto := reflect.ValueOf(into) 15 | if rinto.Kind() != reflect.Ptr { 16 | panic(fmt.Errorf("second argument to UnsafeCast must be a pointer to an interface; received %v", rinto.Type())) 17 | } 18 | rinto = rinto.Elem() 19 | if rinto.Kind() != reflect.Interface { 20 | panic(fmt.Errorf("second argument to UnsafeCast must be a pointer to an interface; received pointer to %v", rinto.Type())) 21 | } 22 | 23 | rfrom := reflect.ValueOf(from) 24 | 25 | // If rfrom is essentially nil, set into to nil and return. 26 | if !rfrom.IsValid() || rfrom.IsZero() { 27 | null := reflect.Zero(rinto.Type()) 28 | rinto.Set(null) 29 | return 30 | } 31 | // Interfaces may present as a pointer to an implementing struct, and that's fine... 32 | if rfrom.Kind() != reflect.Interface && rfrom.Kind() != reflect.Ptr { 33 | panic(fmt.Errorf("first argument to UnsafeCast must be an interface value; received %v", rfrom.Type())) 34 | } 35 | 36 | // If rfrom can be directly converted to rinto, just do it. 37 | if rfrom.Type().AssignableTo(rinto.Type()) { 38 | rfrom = rfrom.Convert(rinto.Type()) 39 | rinto.Set(rfrom) 40 | return 41 | } 42 | 43 | client := kernel.GetClient() 44 | if objID, found := client.FindObjectRef(rfrom); found { 45 | // Ensures the value is initialized properly. Panics if the target value is not a jsii interface type. 46 | client.Types().InitJsiiProxy(rinto, rinto.Type()) 47 | 48 | // If the target type is a behavioral interface, add it to the ObjectRef.Interfaces list. 49 | if fqn, found := client.Types().InterfaceFQN(rinto.Type()); found { 50 | objID.Interfaces = append(objID.Interfaces, fqn) 51 | } 52 | 53 | // Make the new value an alias to the old value. 54 | client.RegisterInstance(rinto, objID) 55 | return 56 | } 57 | 58 | panic(fmt.Errorf("first argument to UnsafeCast must be a jsii proxy value; received %v", rfrom)) 59 | } 60 | -------------------------------------------------------------------------------- /cast_test.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/api" 8 | "github.com/aws/jsii-runtime-go/internal/kernel" 9 | ) 10 | 11 | type MockInterfaceABase interface { 12 | MockMethodABase(_ float64) 13 | } 14 | 15 | type mockABase struct { 16 | _ int // padding 17 | } 18 | 19 | func (m *mockABase) MockMethodABase(_ float64) {} 20 | 21 | type MockInterfaceA interface { 22 | MockInterfaceABase 23 | MockMethodA(_ string) 24 | } 25 | 26 | func NewMockInterfaceA() MockInterfaceA { 27 | return &mockA{mockABase{}} 28 | } 29 | 30 | type mockA struct { 31 | mockABase 32 | } 33 | 34 | func (m *mockA) MockMethodA(_ string) {} 35 | 36 | type MockInterfaceB interface { 37 | MockMethodB(_ int) 38 | } 39 | 40 | func NewMockInterfaceB() MockInterfaceB { 41 | return &mockB{} 42 | } 43 | 44 | type mockB struct { 45 | _ int // Padding 46 | } 47 | 48 | func (m *mockB) MockMethodB(_ int) {} 49 | 50 | func TestNilSource(t *testing.T) { 51 | // Make "into" not nil to ensure the cast function overwrites it. 52 | into := NewMockInterfaceB() 53 | UnsafeCast(nil, &into) 54 | 55 | if into != nil { 56 | t.Fail() 57 | } 58 | } 59 | 60 | func TestSourceAndTargetAreTheSame(t *testing.T) { 61 | into := NewMockInterfaceB() 62 | original := into 63 | UnsafeCast(into, &into) 64 | 65 | if into != original { 66 | t.Fail() 67 | } 68 | } 69 | 70 | func TestTargetIsSubclassOfSource(t *testing.T) { 71 | from := NewMockInterfaceA() 72 | var into MockInterfaceABase 73 | UnsafeCast(from, &into) 74 | 75 | if into != from { 76 | t.Fail() 77 | } 78 | } 79 | 80 | func TestRegistersAlias(t *testing.T) { 81 | client := kernel.GetClient() 82 | 83 | objid := api.ObjectRef{InstanceID: "Object@1337#42"} 84 | from := NewMockInterfaceA() 85 | client.RegisterInstance(reflect.ValueOf(from), objid) 86 | 87 | var into MockInterfaceB 88 | client.Types().RegisterInterface(api.FQN("mock.InterfaceB"), reflect.TypeOf(&into).Elem(), []api.Override{}, func() interface{} { return NewMockInterfaceB() }) 89 | 90 | UnsafeCast(from, &into) 91 | 92 | if into == nil { 93 | t.Fail() 94 | } 95 | 96 | if refid, found := client.FindObjectRef(reflect.ValueOf(into)); !found { 97 | t.Fail() 98 | } else if refid.InstanceID != objid.InstanceID { 99 | t.Fail() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /internal/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | // FQN represents a fully-qualified type name in the jsii type system. 9 | type FQN string 10 | 11 | // Override is a public interface implementing a private method `isOverride` 12 | // implemented by the private custom type `override`. This is embedded by 13 | // MethodOverride and PropertyOverride to simulate the union type of Override = 14 | // MethodOverride | PropertyOverride. 15 | type Override interface { 16 | GoName() string 17 | isOverride() 18 | } 19 | 20 | type override struct{} 21 | 22 | func (o override) isOverride() {} 23 | 24 | // MethodOverride is used to register a "go-native" implementation to be 25 | // substituted to the default javascript implementation on the created object. 26 | type MethodOverride struct { 27 | override 28 | 29 | JsiiMethod string `json:"method"` 30 | GoMethod string `json:"cookie"` 31 | } 32 | 33 | func (m MethodOverride) GoName() string { 34 | return m.GoMethod 35 | } 36 | 37 | // PropertyOverride is used to register a "go-native" implementation to be 38 | // substituted to the default javascript implementation on the created object. 39 | type PropertyOverride struct { 40 | override 41 | 42 | JsiiProperty string `json:"property"` 43 | GoGetter string `json:"cookie"` 44 | } 45 | 46 | func (m PropertyOverride) GoName() string { 47 | return m.GoGetter 48 | } 49 | 50 | func IsMethodOverride(value Override) bool { 51 | switch value.(type) { 52 | case MethodOverride, *MethodOverride: 53 | return true 54 | default: 55 | return false 56 | } 57 | } 58 | 59 | func IsPropertyOverride(value Override) bool { 60 | switch value.(type) { 61 | case PropertyOverride, *PropertyOverride: 62 | return true 63 | default: 64 | return false 65 | } 66 | } 67 | 68 | type ObjectRef struct { 69 | InstanceID string `json:"$jsii.byref"` 70 | Interfaces []FQN `json:"$jsii.interfaces,omitempty"` 71 | } 72 | 73 | func (o *ObjectRef) TypeFQN() FQN { 74 | re := regexp.MustCompile(`^(.+)@(\d+)$`) 75 | if parts := re.FindStringSubmatch(o.InstanceID); parts == nil { 76 | panic(fmt.Errorf("invalid instance id: %#v", o.InstanceID)) 77 | } else { 78 | return FQN(parts[1]) 79 | } 80 | } 81 | 82 | type EnumRef struct { 83 | MemberFQN string `json:"$jsii.enum"` 84 | } 85 | 86 | type WireDate struct { 87 | Timestamp string `json:"$jsii.date"` 88 | } 89 | 90 | type WireMap struct { 91 | MapData map[string]interface{} `json:"$jsii.map"` 92 | } 93 | 94 | type WireStruct struct { 95 | StructDescriptor `json:"$jsii.struct"` 96 | } 97 | 98 | type StructDescriptor struct { 99 | FQN FQN `json:"fqn"` 100 | Fields map[string]interface{} `json:"data"` 101 | } 102 | -------------------------------------------------------------------------------- /internal/kernel/manage-object.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/aws/jsii-runtime-go/internal/api" 9 | ) 10 | 11 | const objectFQN = "Object" 12 | 13 | func (c *Client) ManageObject(v reflect.Value) (ref api.ObjectRef, err error) { 14 | // Ensuring we use a pointer, so we can see pointer-receiver methods, too. 15 | var vt reflect.Type 16 | if v.Kind() == reflect.Interface || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Interface) { 17 | vt = reflect.Indirect(reflect.ValueOf(v.Interface())).Addr().Type() 18 | } else { 19 | vt = reflect.Indirect(v).Addr().Type() 20 | } 21 | interfaces, overrides := c.Types().DiscoverImplementation(vt) 22 | 23 | found := make(map[string]bool) 24 | for _, override := range overrides { 25 | if prop, ok := override.(*api.PropertyOverride); ok { 26 | found[prop.JsiiProperty] = true 27 | } 28 | } 29 | overrides = appendExportedProperties(vt, overrides, found) 30 | 31 | var resp CreateResponse 32 | resp, err = c.Create(CreateProps{ 33 | FQN: objectFQN, 34 | Interfaces: interfaces, 35 | Overrides: overrides, 36 | }) 37 | 38 | if err == nil { 39 | if err = c.objects.Register(v, api.ObjectRef{InstanceID: resp.InstanceID, Interfaces: interfaces}); err == nil { 40 | ref.InstanceID = resp.InstanceID 41 | } 42 | } 43 | 44 | return 45 | } 46 | 47 | func appendExportedProperties(vt reflect.Type, overrides []api.Override, found map[string]bool) []api.Override { 48 | if vt.Kind() == reflect.Ptr { 49 | vt = vt.Elem() 50 | } 51 | 52 | if vt.Kind() == reflect.Struct { 53 | for idx := 0; idx < vt.NumField(); idx++ { 54 | field := vt.Field(idx) 55 | // Unexported fields are not relevant here... 56 | if !field.IsExported() { 57 | continue 58 | } 59 | 60 | // Anonymous fields are embed, we traverse them for fields, too... 61 | if field.Anonymous { 62 | overrides = appendExportedProperties(field.Type, overrides, found) 63 | continue 64 | } 65 | 66 | jsonName := field.Tag.Get("json") 67 | if jsonName == "-" { 68 | // Explicit omit via `json:"-"` 69 | continue 70 | } else if jsonName != "" { 71 | // There could be attributes after the field name (e.g. `json:"foo,omitempty"`) 72 | jsonName = strings.Split(jsonName, ",")[0] 73 | } 74 | // The default behavior is to use the field name as-is in JSON. 75 | if jsonName == "" { 76 | jsonName = field.Name 77 | } 78 | 79 | if !found[jsonName] { 80 | overrides = append(overrides, &api.PropertyOverride{ 81 | JsiiProperty: jsonName, 82 | // Using the "." prefix to signify this isn't actually a getter, just raw field access. 83 | GoGetter: fmt.Sprintf(".%s", field.Name), 84 | }) 85 | found[jsonName] = true 86 | } 87 | } 88 | } 89 | 90 | return overrides 91 | } 92 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type basicType interface { 9 | bool | string | float64 | time.Time 10 | } 11 | 12 | // Ptr returns a pointer to the provided value. 13 | func Ptr[T basicType](v T) *T { 14 | return &v 15 | } 16 | 17 | // PtrSlice returns a pointer to a slice of pointers to all of the provided values. 18 | func PtrSlice[T basicType](v ...T) *[]*T { 19 | slice := make([]*T, len(v)) 20 | for i := 0; i < len(v); i++ { 21 | slice[i] = Ptr(v[i]) 22 | } 23 | return &slice 24 | } 25 | 26 | // Bool returns a pointer to the provided bool. 27 | func Bool(v bool) *bool { return Ptr(v) } 28 | 29 | // Bools returns a pointer to a slice of pointers to all of the provided booleans. 30 | func Bools(v ...bool) *[]*bool { 31 | return PtrSlice(v...) 32 | } 33 | 34 | type numberType interface { 35 | ~float32 | ~float64 | 36 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 37 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 38 | } 39 | 40 | // Number returns a pointer to the provided float64. 41 | func Number[T numberType](v T) *float64 { 42 | return Ptr(float64(v)) 43 | } 44 | 45 | // Numbers returns a pointer to a slice of pointers to all of the provided numbers. 46 | func Numbers[T numberType](v ...T) *[]*float64 { 47 | slice := make([]*float64, len(v)) 48 | for i := 0; i < len(v); i++ { 49 | slice[i] = Number(v[i]) 50 | } 51 | return &slice 52 | } 53 | 54 | // String returns a pointer to the provided string. 55 | func String(v string) *string { return Ptr(v) } 56 | 57 | // Sprintf returns a pointer to a formatted string (semantics are the same as fmt.Sprintf). 58 | func Sprintf(format string, a ...interface{}) *string { 59 | res := fmt.Sprintf(format, a...) 60 | return &res 61 | } 62 | 63 | // Strings returns a pointer to a slice of pointers to all of the provided strings. 64 | func Strings(v ...string) *[]*string { 65 | return PtrSlice(v...) 66 | } 67 | 68 | // Time returns a pointer to the provided time.Time. 69 | func Time(v time.Time) *time.Time { return Ptr(v) } 70 | 71 | // Times returns a pointer to a slice of pointers to all of the provided time.Time values. 72 | func Times(v ...time.Time) *[]*time.Time { 73 | return PtrSlice(v...) 74 | } 75 | 76 | // AnyStrings returns a pointer to a slice of any containing all the provided strings. 77 | func AnyStrings(v ...string) *[]interface{} { 78 | slice := make([]interface{}, len(v)) 79 | for i := 0; i < len(v); i++ { 80 | slice[i] = v[i] 81 | } 82 | return &slice 83 | } 84 | 85 | // AnyNumbers returns a pointer to a slice of any containing all the provided numbers. 86 | func AnyNumbers[T numberType](v ...T) *[]interface{} { 87 | slice := make([]interface{}, len(v)) 88 | for i := 0; i < len(v); i++ { 89 | slice[i] = float64(v[i]) 90 | } 91 | return &slice 92 | } 93 | 94 | // AnySlice converts a pointer to a slice of pointers to a pointer to a slice of Any. 95 | func AnySlice[T any](v *[]*T) *[]interface{} { 96 | if v == nil { 97 | return nil 98 | } 99 | slice := make([]interface{}, len(*v)) 100 | for i, ptr := range *v { 101 | if ptr != nil { 102 | slice[i] = *ptr 103 | } 104 | } 105 | return &slice 106 | } 107 | -------------------------------------------------------------------------------- /helpers_test.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestV(t *testing.T) { 11 | // Bool 12 | assert.Equal(t, true, *Ptr(true)) 13 | assert.Equal(t, false, *Ptr(false)) 14 | // Bools 15 | assert.Equal(t, []*bool{Bool(true), Bool(false), Bool(false), Bool(true)}, *PtrSlice(true, false, false, true)) 16 | // Float64 is supported because it doesn't require conversion. 17 | assert.Equal(t, 123.45, *Ptr(123.45)) 18 | assert.Equal(t, float64(123.45), *Ptr(float64(123.45))) 19 | // String 20 | assert.Equal(t, "Hello", *String("Hello")) 21 | // Strings 22 | assert.Equal(t, []*string{String("Hello"), String("World")}, *Strings("Hello", "World")) 23 | // Time 24 | now := time.Now() 25 | assert.Equal(t, now, *Time(now)) 26 | // Times 27 | assert.Equal(t, []*time.Time{Time(now)}, *Times(now)) 28 | // AnyStrings 29 | assert.Equal(t, []interface{}{"Hello", "World"}, *AnyStrings("Hello", "World")) 30 | // AnyNumbers 31 | assert.Equal(t, []interface{}{float64(42), float64(1337)}, *AnyNumbers(42, 1337)) 32 | // AnySlice 33 | assert.Equal(t, []interface{}{"hello", "world"}, *AnySlice(Strings("hello", "world"))) 34 | } 35 | 36 | func TestBool(t *testing.T) { 37 | assert.Equal(t, true, *Bool(true)) 38 | assert.Equal(t, false, *Bool(false)) 39 | } 40 | 41 | func TestBools(t *testing.T) { 42 | assert.Equal(t, []*bool{Bool(true), Bool(false), Bool(false), Bool(true)}, *Bools(true, false, false, true)) 43 | } 44 | 45 | func TestNumber(t *testing.T) { 46 | assert.Equal(t, 123.45, *Number(123.45)) 47 | assert.Equal(t, 1337.0, *Number(1337)) 48 | // Floats. 49 | assert.Equal(t, float64(float32(123.45)), *Number(float32(123.45))) 50 | assert.Equal(t, float64(123.45), *Number(float64(123.45))) 51 | // Ints. 52 | assert.Equal(t, float64(1337), *Number(int(1337))) 53 | // Signed. 54 | assert.Equal(t, float64(127), *Number(int8(127))) 55 | assert.Equal(t, float64(1337), *Number(int16(1337))) 56 | assert.Equal(t, float64(1337), *Number(int32(1337))) 57 | assert.Equal(t, float64(1337), *Number(int64(1337))) 58 | // Unsigned. 59 | assert.Equal(t, float64(127), *Number(uint8(127))) 60 | assert.Equal(t, float64(1337), *Number(uint16(1337))) 61 | assert.Equal(t, float64(1337), *Number(uint32(1337))) 62 | assert.Equal(t, float64(1337), *Number(uint64(1337))) 63 | } 64 | 65 | func TestNumbers(t *testing.T) { 66 | assert.Equal(t, []*float64{Number(42), Number(1337)}, *Numbers(42, 1337)) 67 | } 68 | 69 | func TestString(t *testing.T) { 70 | assert.Equal(t, "Hello", *String("Hello")) 71 | } 72 | 73 | func TestSprintf(t *testing.T) { 74 | assert.Equal(t, "formatted: 42", *Sprintf("formatted: %d", 42)) 75 | } 76 | 77 | func TestStrings(t *testing.T) { 78 | assert.Equal(t, []*string{String("Hello"), String("World")}, *Strings("Hello", "World")) 79 | } 80 | 81 | func TestTime(t *testing.T) { 82 | now := time.Now() 83 | assert.Equal(t, now, *Time(now)) 84 | } 85 | 86 | func TestTimes(t *testing.T) { 87 | now := time.Now() 88 | assert.Equal(t, []*time.Time{Time(now)}, *Times(now)) 89 | } 90 | 91 | func TestAnyStrings(t *testing.T) { 92 | assert.Equal(t, []interface{}{"Hello", "World"}, *AnyStrings("Hello", "World")) 93 | } 94 | 95 | func TestAnyNumbers(t *testing.T) { 96 | assert.Equal(t, []interface{}{float64(42), float64(1337)}, *AnyNumbers(42, 1337)) 97 | } 98 | 99 | func TestAnySlice(t *testing.T) { 100 | // Test with *[]*string 101 | strings := []*string{String("hello"), String("world")} 102 | result := AnySlice(&strings) 103 | assert.Equal(t, []interface{}{"hello", "world"}, *result) 104 | 105 | // Test with strings 106 | result2 := AnySlice(Strings("hello", "world")) 107 | assert.Equal(t, []interface{}{"hello", "world"}, *result2) 108 | 109 | // Test with *[]*float64 110 | floats := []*float64{Number(1.5), Number(2.5)} 111 | result3 := AnySlice(&floats) 112 | assert.Equal(t, []interface{}{1.5, 2.5}, *result3) 113 | 114 | // Test with Numbers 115 | result4 := AnySlice(Numbers(1.5, 2.5)) 116 | assert.Equal(t, []interface{}{1.5, 2.5}, *result4) 117 | 118 | // Test with nil 119 | assert.Nil(t, AnySlice((*[]*string)(nil))) 120 | } 121 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 2 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 6 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 7 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 10 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 11 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 12 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 13 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 17 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 18 | github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= 19 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 20 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 21 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 22 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 23 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 24 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 25 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 26 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 27 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 28 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 29 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 31 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 32 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 33 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 34 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 37 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 38 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 39 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 40 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 41 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 42 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /internal/kernel/client.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "sync" 8 | 9 | "github.com/aws/jsii-runtime-go/internal/api" 10 | "github.com/aws/jsii-runtime-go/internal/kernel/process" 11 | "github.com/aws/jsii-runtime-go/internal/objectstore" 12 | "github.com/aws/jsii-runtime-go/internal/typeregistry" 13 | ) 14 | 15 | var ( 16 | clientInstance *Client 17 | clientInstanceMutex sync.Mutex 18 | clientOnce sync.Once 19 | types *typeregistry.TypeRegistry = typeregistry.New() 20 | ) 21 | 22 | // The Client struct owns the jsii child process and its io interfaces. It also 23 | // owns a map (objects) that tracks all object references by ID. This is used 24 | // to call methods and access properties on objects passed by the runtime 25 | // process by reference. 26 | type Client struct { 27 | process *process.Process 28 | objects *objectstore.ObjectStore 29 | 30 | // Supports the idempotency of the Load method. 31 | loaded map[LoadProps]LoadResponse 32 | } 33 | 34 | // GetClient returns a singleton Client instance, initializing one the first 35 | // time it is called. 36 | func GetClient() *Client { 37 | clientOnce.Do(func() { 38 | // Locking early to be safe with a concurrent Close execution 39 | clientInstanceMutex.Lock() 40 | defer clientInstanceMutex.Unlock() 41 | 42 | client, err := newClient() 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | clientInstance = client 48 | }) 49 | 50 | return clientInstance 51 | } 52 | 53 | // CloseClient finalizes the runtime process, signalling the end of the 54 | // execution to the jsii kernel process, and waiting for graceful termination. 55 | // 56 | // If a jsii Client is used *after* CloseClient was called, a new jsii kernel 57 | // process will be initialized, and CloseClient should be called again to 58 | // correctly finalize that, too. 59 | func CloseClient() { 60 | // Locking early to be safe with a concurrent getClient execution 61 | clientInstanceMutex.Lock() 62 | defer clientInstanceMutex.Unlock() 63 | 64 | // Reset the "once" so a new Client would get initialized next time around 65 | clientOnce = sync.Once{} 66 | 67 | if clientInstance != nil { 68 | // Close the Client & reset it 69 | clientInstance.close() 70 | clientInstance = nil 71 | } 72 | } 73 | 74 | // newClient initializes a client, making it ready for business. 75 | func newClient() (*Client, error) { 76 | if process, err := process.NewProcess(fmt.Sprintf("^%v", version)); err != nil { 77 | return nil, err 78 | } else { 79 | result := &Client{ 80 | process: process, 81 | objects: objectstore.New(), 82 | loaded: make(map[LoadProps]LoadResponse), 83 | } 84 | 85 | // Register a finalizer to call Close() 86 | runtime.SetFinalizer(result, func(c *Client) { 87 | c.close() 88 | }) 89 | 90 | return result, nil 91 | } 92 | } 93 | 94 | func (c *Client) Types() *typeregistry.TypeRegistry { 95 | return types 96 | } 97 | 98 | func (c *Client) RegisterInstance(instance reflect.Value, objectRef api.ObjectRef) error { 99 | return c.objects.Register(instance, objectRef) 100 | } 101 | 102 | func (c *Client) request(req kernelRequester, res kernelResponder) error { 103 | return c.process.Request(req, res) 104 | } 105 | 106 | func (c *Client) FindObjectRef(obj reflect.Value) (ref api.ObjectRef, found bool) { 107 | ref = api.ObjectRef{} 108 | found = false 109 | 110 | switch obj.Kind() { 111 | case reflect.Struct: 112 | // Structs can be checked only if they are addressable, meaning 113 | // they are obtained from fields of an addressable struct. 114 | if !obj.CanAddr() { 115 | return 116 | } 117 | obj = obj.Addr() 118 | fallthrough 119 | case reflect.Interface, reflect.Ptr: 120 | if ref.InstanceID, found = c.objects.InstanceID(obj); found { 121 | ref.Interfaces = c.objects.Interfaces(ref.InstanceID) 122 | } 123 | return 124 | default: 125 | // Other types cannot possibly be object references! 126 | return 127 | } 128 | } 129 | 130 | func (c *Client) GetObject(objref api.ObjectRef) interface{} { 131 | if obj, ok := c.objects.GetObject(objref.InstanceID); ok { 132 | return obj.Interface() 133 | } 134 | panic(fmt.Errorf("no object found for ObjectRef %v", objref)) 135 | } 136 | 137 | func (c *Client) close() { 138 | c.process.Close() 139 | 140 | // We no longer need a finalizer to run 141 | runtime.SetFinalizer(c, nil) 142 | } 143 | -------------------------------------------------------------------------------- /internal/kernel/process/process_test.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | //go:embed jsii-mock-runtime.js 15 | var mockRuntimeSource []byte 16 | var mockRuntime string 17 | 18 | func TestMain(m *testing.M) { 19 | file, _ := ioutil.TempFile("", "jsii-mock-runtime.*.js") 20 | if _, err := file.Write(mockRuntimeSource); err != nil { 21 | panic(err) 22 | } 23 | file.Close() 24 | mockRuntime = file.Name() 25 | 26 | status := m.Run() 27 | 28 | // Clean up temporary file 29 | os.Remove(mockRuntime) 30 | 31 | os.Exit(status) 32 | } 33 | 34 | // TestVersionCheck ensures the version check logic works correctly. As a side 35 | // benefit, it validates we are correctly launching custom JSII_RUNTIME processes 36 | // as this relies on a mock implementation. 37 | func TestVersionCheck(t *testing.T) { 38 | acceptedVersions := []string{"4.3.2", "4.3.1337", "4.1337.42"} 39 | 40 | for _, mockVersion := range acceptedVersions { 41 | t.Run(fmt.Sprintf("accepts version %v", mockVersion), func(t *testing.T) { 42 | oldJsiiRuntime := os.Getenv(JSII_RUNTIME) 43 | if runtime, err := makeCustomRuntime(mockVersion); err != nil { 44 | t.Fatal(err) 45 | } else { 46 | os.Setenv(JSII_RUNTIME, runtime) 47 | } 48 | defer os.Setenv(JSII_RUNTIME, oldJsiiRuntime) 49 | 50 | process, err := NewProcess(fmt.Sprintf("^4.3.2")) 51 | if err != nil { 52 | t.Fatal(err) 53 | return 54 | } 55 | defer process.Close() 56 | t.Logf("Subprocess command: %v", strings.Join(process.cmd.Args, " ")) 57 | 58 | var ( 59 | request = EchoRequest{Message: "Oh, hi!"} 60 | response EchoResponse 61 | ) 62 | if err := process.Request(request, &response); err != nil { 63 | t.Fatal(err) 64 | } 65 | if response.Message != request.Message { 66 | t.Errorf("Expected %v, received %v", request.Message, response.Message) 67 | } 68 | }) 69 | } 70 | 71 | rejectedVersions := []string{"3.1337.42", "5.0.0-0", "4.3.2-pre.0", "4.3.3-pre.0"} 72 | 73 | for _, mockVersion := range rejectedVersions { 74 | t.Run(fmt.Sprintf("rejects version %v", mockVersion), func(t *testing.T) { 75 | oldJsiiRuntime := os.Getenv(JSII_RUNTIME) 76 | if runtime, err := makeCustomRuntime(mockVersion); err != nil { 77 | t.Fatal(err) 78 | } else { 79 | os.Setenv(JSII_RUNTIME, runtime) 80 | } 81 | defer os.Setenv(JSII_RUNTIME, oldJsiiRuntime) 82 | 83 | process, err := NewProcess("^4.3.2") 84 | if err != nil { 85 | t.Fatal(err) 86 | return 87 | } 88 | defer process.Close() 89 | t.Logf("Subprocess command: %v", strings.Join(process.cmd.Args, " ")) 90 | 91 | var ( 92 | request = EchoRequest{Message: "Oh, hi!"} 93 | response EchoResponse 94 | ) 95 | if err := process.Request(request, &response); err == nil { 96 | t.Errorf("expected an error, but no error received") 97 | } else if !strings.Contains(err.Error(), "incompatible runtime version") { 98 | t.Errorf("expected incompatible runtime version error, got %v", err.Error()) 99 | } 100 | }) 101 | } 102 | } 103 | 104 | func TestJSIINode(t *testing.T) { 105 | t.Run("successful node invocation", func(t *testing.T) { 106 | node, err := getNodePath() 107 | if err != nil { 108 | t.Fatal(err) 109 | return 110 | } 111 | 112 | oldJsiiNode := os.Getenv(JSII_NODE) 113 | os.Setenv(JSII_NODE, node) 114 | defer os.Setenv(JSII_NODE, oldJsiiNode) 115 | 116 | process, err := NewProcess("0.0.0") 117 | if err != nil { 118 | t.Fatal(err) 119 | return 120 | } 121 | defer process.Close() 122 | 123 | err = process.Request(TestRequest{"stats"}, &TestResponse{}) 124 | if err != nil { 125 | t.Fatal(err) 126 | return 127 | } 128 | }) 129 | 130 | t.Run("unsuccessful node invocation", func(t *testing.T) { 131 | oldJsiiNode := os.Getenv(JSII_NODE) 132 | os.Setenv(JSII_NODE, "./absolute-gibberish") 133 | defer os.Setenv(JSII_NODE, oldJsiiNode) 134 | 135 | process, err := NewProcess("0.0.0") 136 | if err != nil { 137 | t.Fatal(err) 138 | return 139 | } 140 | defer process.Close() 141 | 142 | if err := process.Request(TestRequest{"stats"}, &TestResponse{}); err == nil { 143 | t.Errorf("expected an error, but no error received") 144 | } else if !strings.Contains(err.Error(), "no such file or directory") && !strings.Contains(err.Error(), "file does not exist") && !strings.Contains(err.Error(), "file not found") { 145 | // We have 3 options here because Windows returns a different error message, of course... 146 | t.Errorf("expected 'no such file or directory' or 'file does not exist' or 'file not found', got %v", err.Error()) 147 | } 148 | }) 149 | } 150 | 151 | type TestRequest struct { 152 | Api string `json:"api"` 153 | } 154 | 155 | type TestResponse struct { 156 | } 157 | 158 | type EchoRequest struct { 159 | Message string `json:"message"` 160 | } 161 | 162 | type EchoResponse struct { 163 | Message string `json:"message"` 164 | } 165 | 166 | func makeCustomRuntime(mockVersion string) (string, error) { 167 | node, err := getNodePath() 168 | if err != nil { 169 | return "", err 170 | } 171 | 172 | return fmt.Sprintf("%v %v %v", node, mockRuntime, mockVersion), nil 173 | } 174 | 175 | func getNodePath() (string, error) { 176 | var ( 177 | node string 178 | err error 179 | ) 180 | if runtime.GOOS == "windows" { 181 | node, err = exec.LookPath("node.exe") 182 | } else { 183 | node, err = exec.LookPath("node") 184 | } 185 | 186 | return node, err 187 | } 188 | -------------------------------------------------------------------------------- /deprecated.go: -------------------------------------------------------------------------------- 1 | package jsii 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/aws/jsii-runtime-go/internal/api" 7 | "github.com/aws/jsii-runtime-go/runtime" 8 | ) 9 | 10 | // Deprecated: FQN represents a fully-qualified type name in the jsii type system. 11 | type FQN api.FQN 12 | 13 | // Deprecated: Member is a runtime descriptor for a class or interface member 14 | type Member interface { 15 | asRuntimeMember() runtime.Member 16 | } 17 | 18 | // Deprecated: MemberMethod is a runtime descriptor for a class method (implementation of Member) 19 | type MemberMethod api.MethodOverride 20 | 21 | func (m MemberMethod) asRuntimeMember() runtime.Member { 22 | return runtime.MemberMethod(m) 23 | } 24 | 25 | // Deprecated: MemberProperty is a runtime descriptor for a class or interface property (implementation of Member) 26 | type MemberProperty api.PropertyOverride 27 | 28 | func (m MemberProperty) asRuntimeMember() runtime.Member { 29 | return runtime.MemberProperty(m) 30 | } 31 | 32 | // Deprecated: Load ensures a npm package is loaded in the jsii kernel. 33 | func Load(name string, version string, tarball []byte) { 34 | runtime.Load(name, version, tarball) 35 | } 36 | 37 | // Deprecated: RegisterClass associates a class fully qualified name to the specified class 38 | // interface, member list, and proxy maker function. Panics if class is not a go 39 | // interface, or if the provided fqn was already used to register a different type. 40 | func RegisterClass(fqn FQN, class reflect.Type, members []Member, maker func() interface{}) { 41 | rm := make([]runtime.Member, len(members)) 42 | for i, m := range members { 43 | rm[i] = m.asRuntimeMember() 44 | } 45 | runtime.RegisterClass(runtime.FQN(fqn), class, rm, maker) 46 | } 47 | 48 | // Deprecated: RegisterEnum associates an enum's fully qualified name to the specified enum 49 | // type, and members. Panics if enum is not a reflect.String type, any value in 50 | // the provided members map is of a type other than enum, or if the provided 51 | // fqn was already used to register a different type. 52 | func RegisterEnum(fqn FQN, enum reflect.Type, members map[string]interface{}) { 53 | runtime.RegisterEnum(runtime.FQN(fqn), enum, members) 54 | } 55 | 56 | // Deprecated: RegisterInterface associates an interface's fully qualified name to the 57 | // specified interface type, member list, and proxy maker function. Panics if iface is not 58 | // an interface, or if the provided fqn was already used to register a different type. 59 | func RegisterInterface(fqn FQN, iface reflect.Type, members []Member, maker func() interface{}) { 60 | rm := make([]runtime.Member, len(members)) 61 | for i, m := range members { 62 | rm[i] = m.asRuntimeMember() 63 | } 64 | runtime.RegisterInterface(runtime.FQN(fqn), iface, rm, maker) 65 | } 66 | 67 | // Deprecated: RegisterStruct associates a struct's fully qualified name to the specified 68 | // struct type. Panics if strct is not a struct, or if the provided fqn was 69 | // already used to register a different type. 70 | func RegisterStruct(fqn FQN, strct reflect.Type) { 71 | runtime.RegisterStruct(runtime.FQN(fqn), strct) 72 | } 73 | 74 | // Deprecated: InitJsiiProxy initializes a jsii proxy instance at the provided pointer. 75 | // Panics if the pointer cannot be initialized to a proxy instance (i.e: the 76 | // element of it is not a registered jsii interface or class type). 77 | func InitJsiiProxy(ptr interface{}) { 78 | runtime.InitJsiiProxy(ptr) 79 | } 80 | 81 | // Deprecated: Create will construct a new JSII object within the kernel runtime. This is 82 | // called by jsii object constructors. 83 | func Create(fqn FQN, args []interface{}, inst interface{}) { 84 | runtime.Create(runtime.FQN(fqn), args, inst) 85 | } 86 | 87 | // Deprecated: Invoke will call a method on a jsii class instance. The response will be 88 | // decoded into the expected return type for the method being called. 89 | func Invoke(obj interface{}, method string, args []interface{}, ret interface{}) { 90 | runtime.Invoke(obj, method, args, ret) 91 | } 92 | 93 | // Deprecated: InvokeVoid will call a void method on a jsii class instance. 94 | func InvokeVoid(obj interface{}, method string, args []interface{}) { 95 | runtime.InvokeVoid(obj, method, args) 96 | } 97 | 98 | // Deprecated: StaticInvoke will call a static method on a given jsii class. The response 99 | // will be decoded into the expected return type for the method being called. 100 | func StaticInvoke(fqn FQN, method string, args []interface{}, ret interface{}) { 101 | runtime.StaticInvoke(runtime.FQN(fqn), method, args, ret) 102 | } 103 | 104 | // Deprecated: StaticInvokeVoid will call a static void method on a given jsii class. 105 | func StaticInvokeVoid(fqn FQN, method string, args []interface{}) { 106 | runtime.StaticInvokeVoid(runtime.FQN(fqn), method, args) 107 | } 108 | 109 | // Deprecated: Get reads a property value on a given jsii class instance. The response 110 | // should be decoded into the expected type of the property being read. 111 | func Get(obj interface{}, property string, ret interface{}) { 112 | runtime.Get(obj, property, ret) 113 | } 114 | 115 | // Deprecated: StaticGet reads a static property value on a given jsii class. The response 116 | // should be decoded into the expected type of the property being read. 117 | func StaticGet(fqn FQN, property string, ret interface{}) { 118 | runtime.StaticGet(runtime.FQN(fqn), property, ret) 119 | } 120 | 121 | // Deprecated: Set writes a property on a given jsii class instance. The value should match 122 | // the type of the property being written, or the jsii kernel will crash. 123 | func Set(obj interface{}, property string, value interface{}) { 124 | runtime.Set(obj, property, value) 125 | } 126 | 127 | // Deprecated: StaticSet writes a static property on a given jsii class. The value should 128 | // match the type of the property being written, or the jsii kernel will crash. 129 | func StaticSet(fqn FQN, property string, value interface{}) { 130 | runtime.StaticSet(runtime.FQN(fqn), property, value) 131 | } 132 | -------------------------------------------------------------------------------- /internal/typeregistry/typeregistry.go: -------------------------------------------------------------------------------- 1 | package typeregistry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/api" 8 | ) 9 | 10 | // TypeRegistry is used to record runtime type information about the loaded 11 | // modules, which is later used to correctly convert objects received from the 12 | // JavaScript process into native go values. 13 | type TypeRegistry struct { 14 | // fqnToType is used to obtain the native go type for a given jsii fully 15 | // qualified type name. The kind of type being returned depends on what the 16 | // FQN represents... This will be the second argument of provided to a 17 | // register* function. 18 | // enums are not included 19 | fqnToType map[api.FQN]registeredType 20 | 21 | // fqnToEnumMember maps enum member FQNs (e.g. "jsii-calc.StringEnum/A") to 22 | // the corresponding go const for this member. 23 | fqnToEnumMember map[string]interface{} 24 | 25 | // typeToEnumFQN maps Go enum type ("StringEnum") to the corresponding jsii 26 | // enum FQN (e.g. "jsii-calc.StringEnum") 27 | typeToEnumFQN map[reflect.Type]api.FQN 28 | 29 | // typeToInterfaceFQN maps Go interface type ("SomeInterface") to the 30 | // corresponding jsii interface FQN (e.g: "jsii-calc.SomeInterface") 31 | typeToInterfaceFQN map[reflect.Type]api.FQN 32 | 33 | // structInfo maps registered struct types to all their fields, and possibly a validator 34 | structInfo map[reflect.Type]registeredStruct 35 | 36 | // proxyMakers map registered interface types to a proxy maker function. 37 | proxyMakers map[reflect.Type]func() interface{} 38 | 39 | // typeMembers maps each class or interface FQN to the set of members it 40 | // implements in the form of api.Override values. 41 | typeMembers map[api.FQN][]api.Override 42 | } 43 | 44 | type anonymousProxy struct{ _ int } // Padded so it's not 0-sized 45 | 46 | // New creates a new type registry. 47 | func New() *TypeRegistry { 48 | registry := TypeRegistry{ 49 | fqnToType: make(map[api.FQN]registeredType), 50 | fqnToEnumMember: make(map[string]interface{}), 51 | typeToEnumFQN: make(map[reflect.Type]api.FQN), 52 | typeToInterfaceFQN: make(map[reflect.Type]api.FQN), 53 | structInfo: make(map[reflect.Type]registeredStruct), 54 | proxyMakers: make(map[reflect.Type]func() interface{}), 55 | typeMembers: make(map[api.FQN][]api.Override), 56 | } 57 | 58 | // Ensure we can initialize proxies for `interface{}` when a method returns `any`. 59 | registry.proxyMakers[reflect.TypeOf((*interface{})(nil)).Elem()] = func() interface{} { 60 | return &anonymousProxy{} 61 | } 62 | 63 | return ®istry 64 | } 65 | 66 | // IsAnonymousProxy tells whether the value v is an anonymous object proxy, or 67 | // a pointer to one. 68 | func (t *TypeRegistry) IsAnonymousProxy(v interface{}) bool { 69 | _, ok := v.(*anonymousProxy) 70 | if !ok { 71 | _, ok = v.(anonymousProxy) 72 | } 73 | return ok 74 | } 75 | 76 | // StructFields returns the list of fields associated with a jsii struct type, 77 | // the jsii fully qualified type name, and a boolean telling whether the 78 | // provided type was a registered jsii struct type. 79 | func (t *TypeRegistry) StructFields(typ reflect.Type) (fields []reflect.StructField, fqn api.FQN, ok bool) { 80 | var info registeredStruct 81 | if info, ok = t.structInfo[typ]; !ok { 82 | return 83 | } 84 | 85 | fqn = info.FQN 86 | fields = make([]reflect.StructField, len(info.Fields)) 87 | copy(fields, info.Fields) 88 | return 89 | } 90 | 91 | // FindType returns the registered type corresponding to the provided jsii FQN. 92 | func (t *TypeRegistry) FindType(fqn api.FQN) (typ reflect.Type, ok bool) { 93 | var reg registeredType 94 | if reg, ok = t.fqnToType[fqn]; ok { 95 | typ = reg.Type 96 | } 97 | return 98 | } 99 | 100 | // InitJsiiProxy initializes a jsii proxy value at the provided pointer. It 101 | // returns an error if the pointer does not have a value of a registered 102 | // proxyable type (that is, a class or interface type). 103 | func (t *TypeRegistry) InitJsiiProxy(val reflect.Value, valType reflect.Type) error { 104 | switch valType.Kind() { 105 | case reflect.Interface: 106 | if maker, ok := t.proxyMakers[valType]; ok { 107 | made := maker() 108 | val.Set(reflect.ValueOf(made)) 109 | return nil 110 | } 111 | return fmt.Errorf("unable to make an instance of unregistered interface %v", valType) 112 | 113 | case reflect.Struct: 114 | if !val.IsZero() { 115 | return fmt.Errorf("refusing to initialize non-zero-value struct %v", val) 116 | } 117 | numField := valType.NumField() 118 | for i := 0; i < numField; i++ { 119 | field := valType.Field(i) 120 | if field.Name == "_" { 121 | // Ignore any padding 122 | continue 123 | } 124 | if !field.Anonymous { 125 | return fmt.Errorf("refusing to initialize non-anonymous field %v of %v", field.Name, val) 126 | } 127 | if err := t.InitJsiiProxy(val.Field(i), field.Type); err != nil { 128 | return err 129 | } 130 | } 131 | return nil 132 | 133 | default: 134 | return fmt.Errorf("unable to make an instance of %v (neither a struct nor interface)", valType) 135 | } 136 | } 137 | 138 | // EnumMemberForEnumRef returns the go enum member corresponding to a jsii fully 139 | // qualified enum member name (e.g: "jsii-calc.StringEnum/A"). If no enum member 140 | // was registered (via registerEnum) for the provided enumref, an error is 141 | // returned. 142 | func (t *TypeRegistry) EnumMemberForEnumRef(ref api.EnumRef) (interface{}, error) { 143 | if member, ok := t.fqnToEnumMember[ref.MemberFQN]; ok { 144 | return member, nil 145 | } 146 | return nil, fmt.Errorf("no enum member registered for %v", ref.MemberFQN) 147 | } 148 | 149 | // TryRenderEnumRef returns an enumref if the provided value corresponds to a 150 | // registered enum type. The returned enumref is nil if the provided enum value 151 | // is a zero-value (i.e: ""). 152 | func (t *TypeRegistry) TryRenderEnumRef(value reflect.Value) (ref *api.EnumRef, isEnumRef bool) { 153 | if value.Kind() != reflect.String { 154 | isEnumRef = false 155 | return 156 | } 157 | 158 | if enumFQN, ok := t.typeToEnumFQN[value.Type()]; ok { 159 | isEnumRef = true 160 | if memberName := value.String(); memberName != "" { 161 | ref = &api.EnumRef{MemberFQN: fmt.Sprintf("%v/%v", enumFQN, memberName)} 162 | } else { 163 | ref = nil 164 | } 165 | } else { 166 | isEnumRef = false 167 | } 168 | 169 | return 170 | } 171 | 172 | func (t *TypeRegistry) InterfaceFQN(typ reflect.Type) (fqn api.FQN, found bool) { 173 | fqn, found = t.typeToInterfaceFQN[typ] 174 | return 175 | } 176 | -------------------------------------------------------------------------------- /internal/typeregistry/registration.go: -------------------------------------------------------------------------------- 1 | package typeregistry 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/api" 8 | ) 9 | 10 | type typeKind uint8 11 | 12 | const ( 13 | _ = iota 14 | classType typeKind = iota 15 | enumType typeKind = iota 16 | interfaceType typeKind = iota 17 | structType typeKind = iota 18 | ) 19 | 20 | type registeredType struct { 21 | Type reflect.Type 22 | Kind typeKind 23 | } 24 | 25 | type registeredStruct struct { 26 | FQN api.FQN 27 | Fields []reflect.StructField 28 | Validator func(interface{}, func() string) error 29 | } 30 | 31 | // RegisterClass maps the given FQN to the provided class interface, list of 32 | // overrides, and proxy maker function. This returns an error if the class 33 | // type is not a go interface. 34 | func (t *TypeRegistry) RegisterClass(fqn api.FQN, class reflect.Type, overrides []api.Override, maker func() interface{}) error { 35 | if class.Kind() != reflect.Interface { 36 | return fmt.Errorf("the provided class is not an interface: %v", class) 37 | } 38 | 39 | if existing, exists := t.fqnToType[fqn]; exists && existing.Type != class { 40 | return fmt.Errorf("another type was already registered with %v: %v", fqn, existing) 41 | } 42 | 43 | t.fqnToType[fqn] = registeredType{class, classType} 44 | t.proxyMakers[class] = maker 45 | 46 | // Skipping registration if there are no members, as this would have no use. 47 | if len(overrides) > 0 { 48 | t.typeMembers[fqn] = make([]api.Override, len(overrides)) 49 | copy(t.typeMembers[fqn], overrides) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // RegisterEnum maps the given FQN to the provided enum type, and records the 56 | // provided members map (jsii member name => go value). This returns an error 57 | // if the provided enum is not a string derivative, or of any of the provided 58 | // member values has a type other than enm. 59 | func (t *TypeRegistry) RegisterEnum(fqn api.FQN, enm reflect.Type, members map[string]interface{}) error { 60 | if enm.Kind() != reflect.String { 61 | return fmt.Errorf("the provided enum is not a string derivative: %v", enm) 62 | } 63 | if existing, exists := t.fqnToType[fqn]; exists && existing.Type != enm { 64 | return fmt.Errorf("another type was already registered with %v: %v", fqn, existing) 65 | } 66 | if existing, exists := t.typeToEnumFQN[enm]; exists && existing != fqn { 67 | return fmt.Errorf("attempted to re-register %v as %v, but it was registered as %v", enm, fqn, existing) 68 | } 69 | for memberName, memberVal := range members { 70 | vt := reflect.ValueOf(memberVal).Type() 71 | if vt != enm { 72 | return fmt.Errorf("the enum entry for key %v has incorrect type %v", memberName, vt) 73 | } 74 | // Not setting in t.fqnToEnumMember here so we don't cause any side-effects 75 | // if the pre-condition fails at any point. This is done in a second loop. 76 | } 77 | 78 | t.fqnToType[fqn] = registeredType{enm, enumType} 79 | t.typeToEnumFQN[enm] = fqn 80 | for memberName, memberVal := range members { 81 | memberFQN := fmt.Sprintf("%v/%v", fqn, memberName) 82 | t.fqnToEnumMember[memberFQN] = memberVal 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // RegisterInterface maps the given FQN to the provided interface type, list of 89 | // overrides, and proxy maker function. Returns an error if the provided interface 90 | // is not a go interface. 91 | func (t *TypeRegistry) RegisterInterface(fqn api.FQN, iface reflect.Type, overrides []api.Override, maker func() interface{}) error { 92 | if iface.Kind() != reflect.Interface { 93 | return fmt.Errorf("the provided interface is not an interface: %v", iface) 94 | } 95 | 96 | if existing, exists := t.fqnToType[fqn]; exists && existing.Type != iface { 97 | return fmt.Errorf("another type was already registered with %v: %v", fqn, existing) 98 | } 99 | 100 | if existing, exists := t.typeToInterfaceFQN[iface]; exists && existing != fqn { 101 | return fmt.Errorf("anoter FQN was already registered with %v: %v", iface, existing) 102 | } 103 | 104 | t.fqnToType[fqn] = registeredType{iface, interfaceType} 105 | t.typeToInterfaceFQN[iface] = fqn 106 | t.proxyMakers[iface] = maker 107 | 108 | // Skipping registration if there are no members, as this would have no use. 109 | if len(overrides) > 0 { 110 | t.typeMembers[fqn] = make([]api.Override, len(overrides)) 111 | copy(t.typeMembers[fqn], overrides) 112 | } 113 | 114 | return nil 115 | } 116 | 117 | // RegisterStruct maps the given FQN to the provided struct type, and struct 118 | // interface. Returns an error if the provided struct type is not a go struct, 119 | // or the provided iface not a go interface. 120 | func (t *TypeRegistry) RegisterStruct(fqn api.FQN, strct reflect.Type) error { 121 | if strct.Kind() != reflect.Struct { 122 | return fmt.Errorf("the provided struct is not a struct: %v", strct) 123 | } 124 | 125 | if existing, exists := t.fqnToType[fqn]; exists && existing.Type != strct { 126 | return fmt.Errorf("another type was already registered with %v: %v", fqn, existing) 127 | } 128 | 129 | if existing, exists := t.structInfo[strct]; exists && existing.FQN != fqn { 130 | return fmt.Errorf("attempting to register type %v as %v, but it was already registered as: %v", strct, fqn, existing.FQN) 131 | } 132 | 133 | numField := strct.NumField() 134 | fields := make([]reflect.StructField, 0, numField) 135 | for i := 0; i < numField; i++ { 136 | field := strct.Field(i) 137 | if field.Anonymous { 138 | return fmt.Errorf("unexpected anonymous field %v in struct %v (%v)", field, fqn, strct) 139 | } 140 | if field.PkgPath != "" { 141 | return fmt.Errorf("unexpected un-exported field %v in struct %v (%v)", field, fqn, strct) 142 | } 143 | if field.Tag.Get("json") == "" { 144 | return fmt.Errorf("missing json tag on struct field %v of %v (%v)", field, fqn, strct) 145 | } 146 | fields = append(fields, field) 147 | } 148 | 149 | t.fqnToType[fqn] = registeredType{strct, structType} 150 | t.structInfo[strct] = registeredStruct{FQN: fqn, Fields: fields} 151 | 152 | return nil 153 | } 154 | 155 | // RegisterStructValidator adds a validator function to an already registered struct type. This is separate call largely 156 | // to maintain backwards compatibility with existing code. 157 | func (t *TypeRegistry) RegisterStructValidator(strct reflect.Type, validator func(interface{}, func() string) error) error { 158 | if strct.Kind() != reflect.Struct { 159 | return fmt.Errorf("the provided struct is not a struct: %v", strct) 160 | } 161 | 162 | info, ok := t.structInfo[strct] 163 | if !ok { 164 | return fmt.Errorf("the provided struct %v is not registered (call RegisterStruct first)", strct) 165 | } 166 | info.Validator = validator 167 | t.structInfo[strct] = info 168 | 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /internal/kernel/callbacks.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/aws/jsii-runtime-go/internal/api" 9 | ) 10 | 11 | type callback struct { 12 | CallbackID string `json:"cbid"` 13 | Cookie string `json:"cookie"` 14 | Invoke *invokeCallback `json:"invoke"` 15 | Get *getCallback `json:"get"` 16 | Set *setCallback `json:"set"` 17 | } 18 | 19 | func (c *callback) handle(result kernelResponder) error { 20 | var ( 21 | retval reflect.Value 22 | err error 23 | ) 24 | if c.Invoke != nil { 25 | retval, err = c.Invoke.handle(c.Cookie) 26 | } else if c.Get != nil { 27 | retval, err = c.Get.handle(c.Cookie) 28 | } else if c.Set != nil { 29 | retval, err = c.Set.handle(c.Cookie) 30 | } else { 31 | return fmt.Errorf("invalid callback object: %v", c) 32 | } 33 | 34 | if err != nil { 35 | return err 36 | } 37 | 38 | type callbackResult struct { 39 | CallbackID string `json:"cbid"` 40 | Result interface{} `json:"result,omitempty"` 41 | Error string `json:"err,omitempty"` 42 | } 43 | type completeRequest struct { 44 | kernelRequester 45 | callbackResult `json:"complete"` 46 | } 47 | 48 | client := GetClient() 49 | request := completeRequest{} 50 | request.CallbackID = c.CallbackID 51 | request.Result = client.CastPtrToRef(retval) 52 | if err != nil { 53 | request.Error = err.Error() 54 | } 55 | return client.request(request, result) 56 | } 57 | 58 | type invokeCallback struct { 59 | Method string `json:"method"` 60 | Arguments []interface{} `json:"args"` 61 | ObjRef api.ObjectRef `json:"objref"` 62 | } 63 | 64 | func (i *invokeCallback) handle(cookie string) (retval reflect.Value, err error) { 65 | client := GetClient() 66 | 67 | receiver := reflect.ValueOf(client.GetObject(i.ObjRef)) 68 | method := receiver.MethodByName(cookie) 69 | 70 | return client.invoke(method, i.Arguments) 71 | } 72 | 73 | type getCallback struct { 74 | Property string `json:"property"` 75 | ObjRef api.ObjectRef `json:"objref"` 76 | } 77 | 78 | func (g *getCallback) handle(cookie string) (retval reflect.Value, err error) { 79 | client := GetClient() 80 | 81 | receiver := reflect.ValueOf(client.GetObject(g.ObjRef)) 82 | 83 | if strings.HasPrefix(cookie, ".") { 84 | // Ready to catch an error if the access panics... 85 | defer func() { 86 | if r := recover(); r != nil { 87 | if err == nil { 88 | var ok bool 89 | if err, ok = r.(error); !ok { 90 | err = fmt.Errorf("%v", r) 91 | } 92 | } else { 93 | // This is not expected - so we panic! 94 | panic(r) 95 | } 96 | } 97 | }() 98 | 99 | // Need to access the underlying struct... 100 | receiver = receiver.Elem() 101 | retval = receiver.FieldByName(cookie[1:]) 102 | 103 | if retval.IsZero() { 104 | // Omit zero-values if a json tag instructs so... 105 | field, _ := receiver.Type().FieldByName(cookie[1:]) 106 | if tag := field.Tag.Get("json"); tag != "" { 107 | for _, attr := range strings.Split(tag, ",")[1:] { 108 | if attr == "omitempty" { 109 | retval = reflect.ValueOf(nil) 110 | break 111 | } 112 | } 113 | } 114 | } 115 | 116 | return 117 | } else { 118 | method := receiver.MethodByName(cookie) 119 | return client.invoke(method, nil) 120 | } 121 | } 122 | 123 | type setCallback struct { 124 | Property string `json:"property"` 125 | Value interface{} `json:"value"` 126 | ObjRef api.ObjectRef `json:"objref"` 127 | } 128 | 129 | func (s *setCallback) handle(cookie string) (retval reflect.Value, err error) { 130 | client := GetClient() 131 | 132 | receiver := reflect.ValueOf(client.GetObject(s.ObjRef)) 133 | if strings.HasPrefix(cookie, ".") { 134 | // Ready to catch an error if the access panics... 135 | defer func() { 136 | if r := recover(); r != nil { 137 | if err == nil { 138 | var ok bool 139 | if err, ok = r.(error); !ok { 140 | err = fmt.Errorf("%v", r) 141 | } 142 | } else { 143 | // This is not expected - so we panic! 144 | panic(r) 145 | } 146 | } 147 | }() 148 | 149 | // Need to access the underlying struct... 150 | receiver = receiver.Elem() 151 | field := receiver.FieldByName(cookie[1:]) 152 | meta, _ := receiver.Type().FieldByName(cookie[1:]) 153 | 154 | field.Set(convert(reflect.ValueOf(s.Value), meta.Type)) 155 | // Both retval & err are set to zero values here... 156 | return 157 | } else { 158 | method := receiver.MethodByName(fmt.Sprintf("Set%v", cookie)) 159 | return client.invoke(method, []interface{}{s.Value}) 160 | } 161 | } 162 | 163 | func convert(value reflect.Value, typ reflect.Type) reflect.Value { 164 | retry: 165 | vt := value.Type() 166 | 167 | if vt.AssignableTo(typ) { 168 | return value 169 | } 170 | if value.CanConvert(typ) { 171 | return value.Convert(typ) 172 | } 173 | 174 | if typ.Kind() == reflect.Ptr { 175 | switch value.Kind() { 176 | case reflect.String: 177 | str := value.String() 178 | value = reflect.ValueOf(&str) 179 | case reflect.Bool: 180 | bool := value.Bool() 181 | value = reflect.ValueOf(&bool) 182 | case reflect.Int: 183 | int := value.Int() 184 | value = reflect.ValueOf(&int) 185 | case reflect.Float64: 186 | float := value.Float() 187 | value = reflect.ValueOf(&float) 188 | default: 189 | iface := value.Interface() 190 | value = reflect.ValueOf(&iface) 191 | } 192 | goto retry 193 | } 194 | 195 | // Unsure what to do... let default behavior happen... 196 | return value 197 | } 198 | 199 | func (c *Client) invoke(method reflect.Value, args []interface{}) (retval reflect.Value, err error) { 200 | if !method.IsValid() { 201 | err = fmt.Errorf("invalid method") 202 | return 203 | } 204 | 205 | // Convert the arguments, if any... 206 | callArgs := make([]reflect.Value, len(args)) 207 | methodType := method.Type() 208 | numIn := methodType.NumIn() 209 | for i, arg := range args { 210 | var argType reflect.Type 211 | if i < numIn { 212 | argType = methodType.In(i) 213 | } else if methodType.IsVariadic() { 214 | argType = methodType.In(i - 1) 215 | } else { 216 | err = fmt.Errorf("too many arguments received %d for %d", len(args), numIn) 217 | return 218 | } 219 | if argType.Kind() == reflect.Ptr { 220 | callArgs[i] = reflect.New(argType.Elem()) 221 | } else { 222 | callArgs[i] = reflect.New(argType) 223 | } 224 | c.castAndSetToPtr(callArgs[i].Elem(), reflect.ValueOf(arg)) 225 | if argType.Kind() != reflect.Ptr { 226 | // The result of `reflect.New` is always a pointer, so if the 227 | // argument is by-value, we have to de-reference it first. 228 | callArgs[i] = callArgs[i].Elem() 229 | } 230 | } 231 | 232 | // Ready to catch an error if the method panics... 233 | defer func() { 234 | if r := recover(); r != nil { 235 | if err == nil { 236 | var ok bool 237 | if err, ok = r.(error); !ok { 238 | err = fmt.Errorf("%v", r) 239 | } 240 | } else { 241 | // This is not expected - so we panic! 242 | panic(r) 243 | } 244 | } 245 | }() 246 | 247 | result := method.Call(callArgs) 248 | switch len(result) { 249 | case 0: 250 | // Nothing to do, retval is already a 0-value. 251 | case 1: 252 | retval = result[0] 253 | default: 254 | err = fmt.Errorf("too many return values: %v", result) 255 | } 256 | return 257 | } 258 | -------------------------------------------------------------------------------- /internal/kernel/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | "strings" 13 | "sync" 14 | 15 | "github.com/Masterminds/semver/v3" 16 | "github.com/aws/jsii-runtime-go/internal/embedded" 17 | ) 18 | 19 | const JSII_NODE string = "JSII_NODE" 20 | const JSII_RUNTIME string = "JSII_RUNTIME" 21 | 22 | type ErrorResponse struct { 23 | Error string `json:"error"` 24 | Stack *string `json:"stack"` 25 | Name *string `json:"name"` 26 | } 27 | 28 | // Process is a simple interface over the child process hosting the 29 | // @jsii/kernel process. It only exposes a very straight-forward 30 | // request/response interface. 31 | type Process struct { 32 | compatibleVersions *semver.Constraints 33 | 34 | cmd *exec.Cmd 35 | tmpdir string 36 | 37 | stdin io.WriteCloser 38 | stdout io.ReadCloser 39 | stderr io.ReadCloser 40 | 41 | requests *json.Encoder 42 | responses *json.Decoder 43 | stderrDone chan bool 44 | 45 | started bool 46 | closed bool 47 | 48 | mutex sync.Mutex 49 | } 50 | 51 | // NewProcess prepares a new child process, but does not start it yet. It will 52 | // be automatically started whenever the client attempts to send a request 53 | // to it. 54 | // 55 | // If the JSII_RUNTIME environment variable is set, this command will be used 56 | // to start the child process, in a sub-shell (using %COMSPEC% or cmd.exe on 57 | // Windows; $SHELL or /bin/sh on other OS'es). Otherwise, the embedded runtime 58 | // application will be extracted into a temporary directory, and used. 59 | // 60 | // The current process' environment is inherited by the child process. Additional 61 | // environment may be injected into the child process' environment - all of which 62 | // with lower precedence than the launching process' environment, with the notable 63 | // exception of JSII_AGENT, which is reserved. 64 | func NewProcess(compatibleVersions string) (*Process, error) { 65 | p := Process{} 66 | 67 | if constraints, err := semver.NewConstraint(compatibleVersions); err != nil { 68 | return nil, err 69 | } else { 70 | p.compatibleVersions = constraints 71 | } 72 | 73 | if custom := os.Getenv(JSII_RUNTIME); custom != "" { 74 | var ( 75 | command string 76 | args []string 77 | ) 78 | // Sub-shelling in order to avoid having to parse arguments 79 | if runtime.GOOS == "windows" { 80 | // On windows, we use %ComSpec% if set, or cmd.exe 81 | if cmd := os.Getenv("ComSpec"); cmd != "" { 82 | command = cmd 83 | } else { 84 | command = "cmd.exe" 85 | } 86 | // The /d option disables Registry-defined AutoRun, it's safer to enable 87 | // The /s option tells cmd.exe the command is quoted as if it were typed into a prompt 88 | // The /c option tells cmd.exe to run the specified command and exit immediately 89 | args = []string{"/d", "/s", "/c", custom} 90 | } else { 91 | // On other OS'es, we use $SHELL and fall back to "/bin/sh" 92 | if shell := os.Getenv("SHELL"); shell != "" { 93 | command = shell 94 | } else { 95 | command = "/bin/sh" 96 | } 97 | args = []string{"-c", custom} 98 | } 99 | p.cmd = exec.Command(command, args...) 100 | } else if tmpdir, err := ioutil.TempDir("", "jsii-runtime.*"); err != nil { 101 | return nil, err 102 | } else { 103 | p.tmpdir = tmpdir 104 | if entrypoint, err := embedded.ExtractRuntime(tmpdir); err != nil { 105 | p.Close() 106 | return nil, err 107 | } else { 108 | if node := os.Getenv(JSII_NODE); node != "" { 109 | p.cmd = exec.Command(node, entrypoint) 110 | } else { 111 | p.cmd = exec.Command("node", entrypoint) 112 | } 113 | } 114 | } 115 | 116 | // Setting up environment - if duplicate keys are found, the last value is used, so we are careful with ordering. In 117 | // particular, we are setting NODE_OPTIONS only if `os.Environ()` does not have another value... So the user can 118 | // control the environment... However, JSII_AGENT must always be controlled by this process. 119 | p.cmd.Env = append([]string{"NODE_OPTIONS=--max-old-space-size=4069"}, os.Environ()...) 120 | p.cmd.Env = append(p.cmd.Env, fmt.Sprintf("JSII_AGENT=%v/%v/%v", runtime.Version(), runtime.GOOS, runtime.GOARCH)) 121 | 122 | if stdin, err := p.cmd.StdinPipe(); err != nil { 123 | p.Close() 124 | return nil, err 125 | } else { 126 | p.stdin = stdin 127 | p.requests = json.NewEncoder(stdin) 128 | } 129 | if stdout, err := p.cmd.StdoutPipe(); err != nil { 130 | p.Close() 131 | return nil, err 132 | } else { 133 | p.stdout = stdout 134 | p.responses = json.NewDecoder(stdout) 135 | } 136 | if stderr, err := p.cmd.StderrPipe(); err != nil { 137 | p.Close() 138 | return nil, err 139 | } else { 140 | p.stderr = stderr 141 | } 142 | 143 | return &p, nil 144 | } 145 | 146 | func (p *Process) ensureStarted() error { 147 | if p.closed { 148 | return fmt.Errorf("this process has been closed") 149 | } 150 | if p.started { 151 | return nil 152 | } 153 | if err := p.cmd.Start(); err != nil { 154 | p.Close() 155 | return err 156 | } 157 | p.started = true 158 | 159 | done := make(chan bool, 1) 160 | go p.consumeStderr(done) 161 | p.stderrDone = done 162 | 163 | var handshake handshakeResponse 164 | if err := p.readResponse(&handshake); err != nil { 165 | p.Close() 166 | return err 167 | } 168 | 169 | if runtimeVersion, err := handshake.runtimeVersion(); err != nil { 170 | p.Close() 171 | return err 172 | } else if ok, errs := p.compatibleVersions.Validate(runtimeVersion); !ok { 173 | causes := make([]string, len(errs)) 174 | for i, err := range errs { 175 | causes[i] = fmt.Sprintf("- %v", err) 176 | } 177 | p.Close() 178 | return fmt.Errorf("incompatible runtime version:\n%v", strings.Join(causes, "\n")) 179 | } 180 | 181 | go func() { 182 | err := p.cmd.Wait() 183 | if err != nil { 184 | fmt.Fprintf(os.Stderr, "Runtime process exited abnormally: %v", err.Error()) 185 | } 186 | p.Close() 187 | }() 188 | 189 | return nil 190 | } 191 | 192 | // Request starts the child process if that has not happened yet, then 193 | // encodes the supplied request and sends it to the child process 194 | // via the requests channel, then decodes the response into the provided 195 | // response pointer. If the process is not in a usable state, or if the 196 | // encoding fails, an error is returned. 197 | func (p *Process) Request(request interface{}, response interface{}) error { 198 | if err := p.ensureStarted(); err != nil { 199 | return err 200 | } 201 | if err := p.requests.Encode(request); err != nil { 202 | p.Close() 203 | return err 204 | } 205 | return p.readResponse(response) 206 | } 207 | 208 | func (p *Process) readResponse(into interface{}) error { 209 | if !p.responses.More() { 210 | return fmt.Errorf("no response received from child process") 211 | } 212 | 213 | var raw json.RawMessage 214 | var respmap map[string]interface{} 215 | err := p.responses.Decode(&raw) 216 | if err != nil { 217 | return err 218 | } 219 | 220 | err = json.Unmarshal(raw, &respmap) 221 | if err != nil { 222 | return err 223 | } 224 | 225 | var errResp ErrorResponse 226 | if _, ok := respmap["error"]; ok { 227 | json.Unmarshal(raw, &errResp) 228 | 229 | if errResp.Name != nil && *errResp.Name == "@jsii/kernel.Fault" { 230 | return fmt.Errorf("JsiiError: %s %s", *errResp.Name, errResp.Error) 231 | } 232 | 233 | return errors.New(errResp.Error) 234 | } 235 | 236 | return json.Unmarshal(raw, &into) 237 | } 238 | 239 | func (p *Process) Close() { 240 | if p.closed { 241 | return 242 | } 243 | 244 | // Acquire the lock, so we don't try to concurrently close multiple times 245 | p.mutex.Lock() 246 | defer p.mutex.Unlock() 247 | 248 | // Check again now that we own the lock, it may be a fast exit! 249 | if p.closed { 250 | return 251 | } 252 | 253 | if p.stdin != nil { 254 | // Try to send the exit message, this might fail, but we can ignore that. 255 | p.stdin.Write([]byte("{\"exit\":0}\n")) 256 | 257 | // Close STDIN for the child process now. Ignoring errors, as it may 258 | // have been closed already (e.g: if the process exited). 259 | p.stdin.Close() 260 | p.stdin = nil 261 | } 262 | 263 | if p.stdout != nil { 264 | // Close STDOUT for the child process now, as we don't expect to receive 265 | // responses anymore. Ignoring errors, as it may have been closed 266 | // already (e.g: if the process exited). 267 | p.stdout.Close() 268 | p.stdout = nil 269 | } 270 | 271 | if p.stderrDone != nil { 272 | // Wait for the stderr sink goroutine to have finished 273 | <-p.stderrDone 274 | p.stderrDone = nil 275 | } 276 | 277 | if p.stderr != nil { 278 | // Close STDERR for the child process now, as we're no longer consuming 279 | // it anyway. Ignoring errors, as it may havebeen closed already (e.g: 280 | // if the process exited). 281 | p.stderr.Close() 282 | p.stderr = nil 283 | } 284 | 285 | if p.cmd != nil { 286 | // Wait for the child process to be dead and gone (should already be) 287 | p.cmd.Wait() 288 | p.cmd = nil 289 | } 290 | 291 | if p.tmpdir != "" { 292 | // Clean up any temporary directory we provisioned. 293 | if err := os.RemoveAll(p.tmpdir); err != nil { 294 | fmt.Fprintf(os.Stderr, "could not clean up temporary directory: %v\n", err) 295 | } 296 | p.tmpdir = "" 297 | } 298 | 299 | p.closed = true 300 | } 301 | -------------------------------------------------------------------------------- /internal/objectstore/objectstore.go: -------------------------------------------------------------------------------- 1 | package objectstore 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/aws/jsii-runtime-go/internal/api" 8 | ) 9 | 10 | // stringSet is a set of strings, implemented as a map from string to an 11 | // arbitrary 0-width value. 12 | type stringSet map[string]struct{} 13 | 14 | // ObjectStore tracks object instances for which an identifier has been 15 | // associated. Object to instanceID association is tracked using the object 16 | // memory address (aka pointer value) in order to not have issues with go's 17 | // standard object equality rules (we need distinct - but possibly equal) object 18 | // instances to be considered as separate entities for our purposes. 19 | type ObjectStore struct { 20 | // objectToID associates an object's memory address (pointer value) with an 21 | // instanceID. This includes aliases (anonymous embedded values) of objects 22 | // passed to the Register method. 23 | objectToID map[uintptr]string 24 | 25 | // idToObject associates an instanceID with the first reflect.Value instance 26 | // that represents the top-level object that was registered with the 27 | // instanceID first via the Register method. 28 | idToObject map[string]reflect.Value 29 | 30 | // idToObjects associates an instanceID with the reflect.Value instances that 31 | // represent the top-level objects that were registered with the instanceID 32 | // via the Register method. 33 | idToObjects map[string]map[reflect.Value]struct{} 34 | 35 | // idToInterfaces associates an instanceID with the set of interfaces that it 36 | // is known to implement. 37 | // 38 | // Incorrect use of the UnsafeCast function may result in an instance's 39 | // interface list containing interfaces that it does not actually implement. 40 | idToInterfaces map[string]stringSet 41 | } 42 | 43 | // New initializes a new ObjectStore. 44 | func New() *ObjectStore { 45 | return &ObjectStore{ 46 | objectToID: make(map[uintptr]string), 47 | idToObject: make(map[string]reflect.Value), 48 | idToObjects: make(map[string]map[reflect.Value]struct{}), 49 | idToInterfaces: make(map[string]stringSet), 50 | } 51 | } 52 | 53 | // Register associates the provided value with the given instanceID. It also 54 | // registers any anonymously embedded value (transitively) against the same 55 | // instanceID, so that methods promoted from those resolve the correct 56 | // instanceID, too. 57 | // 58 | // Returns an error if the provided value is not a pointer value; if the value 59 | // or any of it's (transitively) anonymous embeds have already been registered 60 | // against a different instanceID; of if the provided instanceID was already 61 | // associated to a different value. 62 | // 63 | // The call is idempotent: calling Register again with the same value and 64 | // instanceID does not result in an error. 65 | func (o *ObjectStore) Register(value reflect.Value, objectRef api.ObjectRef) error { 66 | var err error 67 | if value, err = canonicalValue(value); err != nil { 68 | return err 69 | } 70 | ptr := value.Pointer() 71 | 72 | if existing, found := o.objectToID[ptr]; found { 73 | if existing == objectRef.InstanceID { 74 | o.mergeInterfaces(objectRef) 75 | return nil 76 | } 77 | return fmt.Errorf("attempting to register %v as %v, but it was already registered as %v", value, objectRef.InstanceID, existing) 78 | } 79 | 80 | aliases := findAliases(value) 81 | 82 | if existing, found := o.idToObjects[objectRef.InstanceID]; found { 83 | if _, found := existing[value]; found { 84 | o.mergeInterfaces(objectRef) 85 | return nil 86 | } 87 | // Value already exists (e.g: a constructor made a callback with "this" 88 | // passed as an argument). We make the current value(s) an alias of the new 89 | // one. 90 | for existing := range existing { 91 | aliases = append(aliases, existing) 92 | } 93 | } 94 | 95 | for _, alias := range aliases { 96 | ptr := alias.Pointer() 97 | if existing, found := o.objectToID[ptr]; found && existing != objectRef.InstanceID { 98 | return fmt.Errorf("value %v is embedded in %v which has ID %v, but was already assigned %v", alias.String(), value.String(), objectRef.InstanceID, existing) 99 | } 100 | } 101 | 102 | o.objectToID[ptr] = objectRef.InstanceID 103 | // Only add to idToObject if this is the first time this InstanceID is registered 104 | if _, found := o.idToObject[objectRef.InstanceID]; !found { 105 | o.idToObject[objectRef.InstanceID] = value 106 | } 107 | if _, found := o.idToObjects[objectRef.InstanceID]; !found { 108 | o.idToObjects[objectRef.InstanceID] = make(map[reflect.Value]struct{}) 109 | } 110 | o.idToObjects[objectRef.InstanceID][value] = struct{}{} 111 | for _, alias := range aliases { 112 | o.objectToID[alias.Pointer()] = objectRef.InstanceID 113 | } 114 | 115 | o.mergeInterfaces(objectRef) 116 | 117 | return nil 118 | } 119 | 120 | // mergeInterfaces adds all interfaces carried by the provided objectRef to the 121 | // tracking set for the objectRef's InstanceID. Does nothing if no interfaces 122 | // are designated on the objectRef. 123 | func (o *ObjectStore) mergeInterfaces(objectRef api.ObjectRef) { 124 | // If we don't have interfaces, we have nothing to do... 125 | if objectRef.Interfaces == nil { 126 | return 127 | } 128 | 129 | // Find or create the interface list for the relevant InstanceID 130 | var interfaces stringSet 131 | if list, found := o.idToInterfaces[objectRef.InstanceID]; found { 132 | interfaces = list 133 | } else { 134 | interfaces = make(stringSet) 135 | o.idToInterfaces[objectRef.InstanceID] = interfaces 136 | } 137 | 138 | // Add any missing interface to the list. 139 | for _, iface := range objectRef.Interfaces { 140 | interfaces[string(iface)] = struct{}{} 141 | } 142 | } 143 | 144 | // InstanceID attempts to determine the instanceID associated with the provided 145 | // value, if any. Returns the existing instanceID and a boolean informing 146 | // whether an instanceID was already found or not. 147 | // 148 | // The InstanceID method is safe to call with values that are not track-able in 149 | // an ObjectStore (i.e: non-pointer values, primitive values, etc...). 150 | func (o *ObjectStore) InstanceID(value reflect.Value) (instanceID string, found bool) { 151 | var err error 152 | if value, err = canonicalValue(value); err == nil { 153 | ptr := value.Pointer() 154 | instanceID, found = o.objectToID[ptr] 155 | } 156 | return 157 | } 158 | 159 | // Interfaces returns the set of interfaces associated with the provided 160 | // instanceID. 161 | // 162 | // It returns a nil slice in case the instancceID is invalid, or if it does not 163 | // have any associated interfaces. 164 | func (o *ObjectStore) Interfaces(instanceID string) []api.FQN { 165 | if set, found := o.idToInterfaces[instanceID]; found { 166 | interfaces := make([]api.FQN, 0, len(set)) 167 | for iface := range set { 168 | interfaces = append(interfaces, api.FQN(iface)) 169 | } 170 | return interfaces 171 | } else { 172 | return nil 173 | } 174 | } 175 | 176 | // GetObject attempts to retrieve the object value associated with the given 177 | // instanceID. Returns the existing value and a boolean informing whether a 178 | // value was associated with this instanceID or not. 179 | // 180 | // The GetObject method is safe to call with an instanceID that was never 181 | // registered with the ObjectStore. 182 | func (o *ObjectStore) GetObject(instanceID string) (value reflect.Value, found bool) { 183 | value, found = o.idToObject[instanceID] 184 | return 185 | } 186 | 187 | // GetObjectAs attempts to retrieve the object value associated with the given 188 | // instanceID, compatible with the given type. Returns the existing value and a 189 | // boolean informing whether a value was associated with this instanceID and 190 | // compatible with this type or not. 191 | // 192 | // The GetObjectAs method is safe to call with an instanceID that was never 193 | // registered with the ObjectStore. 194 | func (o *ObjectStore) GetObjectAs(instanceID string, typ reflect.Type) (value reflect.Value, found bool) { 195 | found = false 196 | if values, exists := o.idToObjects[instanceID]; exists { 197 | for value = range values { 198 | if value.Type().AssignableTo(typ) { 199 | value = value.Convert(typ) 200 | found = true 201 | return 202 | } 203 | } 204 | } 205 | return 206 | } 207 | 208 | // canonicalValue ensures the same reference is always considered for object 209 | // identity (especially in maps), so that we don't get surprised by pointer to 210 | // struct versus struct value versus opaque interface value, etc... 211 | func canonicalValue(value reflect.Value) (reflect.Value, error) { 212 | if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct { 213 | return value, nil 214 | } 215 | // If this is a pointer to something, de-references it. 216 | result := reflect.ValueOf(reflect.Indirect(value).Interface()) 217 | 218 | if result.Kind() != reflect.Ptr { 219 | return reflect.Value{}, fmt.Errorf("illegal argument: %v is not a pointer", result.String()) 220 | } 221 | 222 | return result, nil 223 | } 224 | 225 | // findAliases traverses the provided object value to recursively identify all 226 | // anonymous embedded values, which will then be registered against the same 227 | // instanceID as the embedding value. 228 | // 229 | // This function assumes the provided value is either a reflect.Struct or a 230 | // pointer to a reflect.Struct (possibly as a reflect.Interface). Calling with 231 | // a nil value, or a value that is not ultimately a reflect.Struct may result 232 | // in panic. 233 | func findAliases(value reflect.Value) []reflect.Value { 234 | var result []reflect.Value 235 | 236 | // Indirect so we always work on the pointer referree 237 | value = reflect.Indirect(value) 238 | 239 | t := value.Type() 240 | numField := t.NumField() 241 | for i := 0; i < numField; i++ { 242 | f := t.Field(i) 243 | 244 | // Ignore non-anonymous fields (including padding) 245 | if !f.Anonymous { 246 | continue 247 | } 248 | 249 | fv := value.FieldByIndex(f.Index) 250 | if fv.Kind() == reflect.Interface { 251 | // If an interface, de-reference to get to the struct type. 252 | fv = reflect.ValueOf(fv.Interface()) 253 | } 254 | if fv.Kind() == reflect.Struct { 255 | // If a struct, get the address of the member. 256 | fv = fv.Addr() 257 | } 258 | 259 | result = append(result, fv) 260 | // Recurse down to collect nested aliases 261 | result = append(result, findAliases(fv)...) 262 | } 263 | 264 | return result 265 | } 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /internal/kernel/conversions.go: -------------------------------------------------------------------------------- 1 | package kernel 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/aws/jsii-runtime-go/internal/api" 9 | ) 10 | 11 | var ( 12 | anyType = reflect.TypeOf((*interface{})(nil)).Elem() 13 | ) 14 | 15 | // CastAndSetToPtr accepts a pointer to any type and attempts to cast the value 16 | // argument to be the same type. Then it sets the value of the pointer element 17 | // to be the newly cast data. This is used to cast payloads from JSII to 18 | // expected return types for Get and Invoke functions. 19 | func (c *Client) CastAndSetToPtr(ptr interface{}, data interface{}) { 20 | ptrVal := reflect.ValueOf(ptr).Elem() 21 | dataVal := reflect.ValueOf(data) 22 | 23 | c.castAndSetToPtr(ptrVal, dataVal) 24 | } 25 | 26 | // castAndSetToPtr is the same as CastAndSetToPtr except it operates on the 27 | // reflect.Value representation of the pointer and data. 28 | func (c *Client) castAndSetToPtr(ptr reflect.Value, data reflect.Value) { 29 | if !data.IsValid() { 30 | // data will not be valid if was made from a nil value, as there would 31 | // not have been enough type information available to build a valid 32 | // reflect.Value. In such cases, we must craft the correctly-typed zero 33 | // value ourselves. 34 | data = reflect.Zero(ptr.Type()) 35 | } else if ptr.Kind() == reflect.Ptr && ptr.IsNil() { 36 | // if ptr is a Pointer type and data is valid, initialize a non-nil pointer 37 | // type. Otherwise inner value is not-settable upon recursion. See third 38 | // law of reflection. 39 | // https://blog.golang.org/laws-of-reflection 40 | ptr.Set(reflect.New(ptr.Type().Elem())) 41 | c.castAndSetToPtr(ptr.Elem(), data) 42 | return 43 | } else if data.Kind() == reflect.Interface && !data.IsNil() { 44 | // If data is a non-nil interface, unwrap it to get it's dynamic value 45 | // type sorted out, so that further calls in this method don't have to 46 | // worry about this edge-case when reasoning on kinds. 47 | data = reflect.ValueOf(data.Interface()) 48 | } 49 | 50 | if ref, isRef := castValToRef(data); isRef { 51 | // If return data is a jsii struct passed by reference, de-reference it all. 52 | if fields, _, isStruct := c.Types().StructFields(ptr.Type()); isStruct { 53 | for _, field := range fields { 54 | got, err := c.Get(GetProps{ 55 | Property: field.Tag.Get("json"), 56 | ObjRef: ref, 57 | }) 58 | if err != nil { 59 | panic(err) 60 | } 61 | fieldVal := ptr.FieldByIndex(field.Index) 62 | c.castAndSetToPtr(fieldVal, reflect.ValueOf(got.Value)) 63 | } 64 | return 65 | } 66 | 67 | targetType := ptr.Type() 68 | if typ, ok := c.Types().FindType(ref.TypeFQN()); ok && typ.AssignableTo(ptr.Type()) { 69 | // Specialize the return type to be the dynamic value type 70 | targetType = typ 71 | } 72 | 73 | // If it's currently tracked, return the current instance 74 | if object, ok := c.objects.GetObjectAs(ref.InstanceID, targetType); ok { 75 | ptr.Set(object) 76 | return 77 | } 78 | 79 | // If return data is jsii object references, add to objects table. 80 | if err := c.Types().InitJsiiProxy(ptr, targetType); err == nil { 81 | if err = c.RegisterInstance(ptr, ref); err != nil { 82 | panic(err) 83 | } 84 | } else { 85 | panic(err) 86 | } 87 | return 88 | } 89 | 90 | if enumref, isEnum := castValToEnumRef(data); isEnum { 91 | member, err := c.Types().EnumMemberForEnumRef(enumref) 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | ptr.Set(reflect.ValueOf(member)) 97 | return 98 | } 99 | 100 | if date, isDate := castValToDate(data); isDate { 101 | ptr.Set(reflect.ValueOf(date)) 102 | return 103 | } 104 | 105 | // maps 106 | if m, isMap := c.castValToMap(data, ptr.Type()); isMap { 107 | ptr.Set(m) 108 | return 109 | } 110 | 111 | // arrays 112 | if data.Kind() == reflect.Slice { 113 | len := data.Len() 114 | var slice reflect.Value 115 | if ptr.Kind() == reflect.Slice { 116 | slice = reflect.MakeSlice(ptr.Type(), len, len) 117 | } else { 118 | slice = reflect.MakeSlice(reflect.SliceOf(anyType), len, len) 119 | } 120 | 121 | // If return type is a slice, recursively cast elements 122 | for i := 0; i < len; i++ { 123 | c.castAndSetToPtr(slice.Index(i), data.Index(i)) 124 | } 125 | 126 | ptr.Set(slice) 127 | return 128 | } 129 | 130 | ptr.Set(data) 131 | } 132 | 133 | // Accepts pointers to structs that implement interfaces and searches for an 134 | // existing object reference in the kernel. If it exists, it casts it to an 135 | // objref for the runtime. Recursively casts types that may contain nested 136 | // object references. 137 | func (c *Client) CastPtrToRef(dataVal reflect.Value) interface{} { 138 | if !dataVal.IsValid() { 139 | // dataVal is a 0-value, meaning we have no value available... We return 140 | // this to JavaScript as a "null" value. 141 | return nil 142 | } 143 | if (dataVal.Kind() == reflect.Interface || dataVal.Kind() == reflect.Ptr) && dataVal.IsNil() { 144 | return nil 145 | } 146 | 147 | // In case we got a time.Time value (or pointer to one). 148 | if wireDate, isDate := castPtrToDate(dataVal); isDate { 149 | return wireDate 150 | } 151 | 152 | switch dataVal.Kind() { 153 | case reflect.Map: 154 | result := api.WireMap{MapData: make(map[string]interface{})} 155 | 156 | iter := dataVal.MapRange() 157 | for iter.Next() { 158 | key := iter.Key().String() 159 | val := iter.Value() 160 | result.MapData[key] = c.CastPtrToRef(val) 161 | } 162 | 163 | return result 164 | 165 | case reflect.Interface, reflect.Ptr: 166 | if valref, valHasRef := c.FindObjectRef(dataVal); valHasRef { 167 | return valref 168 | } 169 | 170 | // In case we got a pointer to a map, slice, enum, ... 171 | if elem := reflect.Indirect(dataVal.Elem()); elem.Kind() != reflect.Struct { 172 | return c.CastPtrToRef(elem) 173 | } 174 | 175 | if dataVal.Elem().Kind() == reflect.Struct { 176 | elemVal := dataVal.Elem() 177 | if fields, fqn, isStruct := c.Types().StructFields(elemVal.Type()); isStruct { 178 | data := make(map[string]interface{}) 179 | for _, field := range fields { 180 | fieldVal := elemVal.FieldByIndex(field.Index) 181 | if (fieldVal.Kind() == reflect.Ptr || fieldVal.Kind() == reflect.Interface) && fieldVal.IsNil() { 182 | // If there is the "field" tag, and it's "required", then panic since the value is nil. 183 | if requiredOrOptional, found := field.Tag.Lookup("field"); found && requiredOrOptional == "required" { 184 | panic(fmt.Sprintf("Field %v.%v is required, but has nil value", field.Type, field.Name)) 185 | } 186 | continue 187 | } 188 | key := field.Tag.Get("json") 189 | data[key] = c.CastPtrToRef(fieldVal) 190 | } 191 | 192 | return api.WireStruct{ 193 | StructDescriptor: api.StructDescriptor{ 194 | FQN: fqn, 195 | Fields: data, 196 | }, 197 | } 198 | } 199 | } else if dataVal.Elem().Kind() == reflect.Ptr { 200 | // Typically happens when a struct pointer is passed into an interface{} 201 | // typed API (such as a place where a union is accepted). 202 | elemVal := dataVal.Elem() 203 | return c.CastPtrToRef(elemVal) 204 | } 205 | 206 | if ref, err := c.ManageObject(dataVal); err != nil { 207 | panic(err) 208 | } else { 209 | return ref 210 | } 211 | 212 | case reflect.Slice: 213 | refs := make([]interface{}, dataVal.Len()) 214 | for i := 0; i < dataVal.Len(); i++ { 215 | refs[i] = c.CastPtrToRef(dataVal.Index(i)) 216 | } 217 | return refs 218 | 219 | case reflect.String: 220 | if enumRef, isEnumRef := c.Types().TryRenderEnumRef(dataVal); isEnumRef { 221 | return enumRef 222 | } 223 | } 224 | return dataVal.Interface() 225 | } 226 | 227 | // castPtrToDate obtains an api.WireDate from the provided reflect.Value if it 228 | // represents a time.Time or *time.Time value. It accepts both a pointer and 229 | // direct value as a convenience (when passing time.Time through an interface{} 230 | // parameter, having to unwrap it as a pointer is annoying and unneeded). 231 | func castPtrToDate(data reflect.Value) (wireDate api.WireDate, ok bool) { 232 | var timestamp *time.Time 233 | if timestamp, ok = data.Interface().(*time.Time); !ok { 234 | var val time.Time 235 | if val, ok = data.Interface().(time.Time); ok { 236 | timestamp = &val 237 | } 238 | } 239 | if ok { 240 | wireDate.Timestamp = timestamp.Format(time.RFC3339Nano) 241 | } 242 | return 243 | } 244 | 245 | func castValToRef(data reflect.Value) (ref api.ObjectRef, ok bool) { 246 | if data.Kind() == reflect.Map { 247 | for _, k := range data.MapKeys() { 248 | // Finding values type requires extracting from reflect.Value 249 | // otherwise .Kind() returns `interface{}` 250 | v := reflect.ValueOf(data.MapIndex(k).Interface()) 251 | 252 | if k.Kind() != reflect.String { 253 | continue 254 | } 255 | 256 | switch k.String() { 257 | case "$jsii.byref": 258 | if v.Kind() != reflect.String { 259 | ok = false 260 | return 261 | } 262 | ref.InstanceID = v.String() 263 | ok = true 264 | case "$jsii.interfaces": 265 | if v.Kind() != reflect.Slice { 266 | continue 267 | } 268 | ifaces := make([]api.FQN, v.Len()) 269 | for i := 0; i < v.Len(); i++ { 270 | e := reflect.ValueOf(v.Index(i).Interface()) 271 | if e.Kind() != reflect.String { 272 | ok = false 273 | return 274 | } 275 | ifaces[i] = api.FQN(e.String()) 276 | } 277 | ref.Interfaces = ifaces 278 | } 279 | 280 | } 281 | } 282 | 283 | return ref, ok 284 | } 285 | 286 | // TODO: This should return a time.Time instead 287 | func castValToDate(data reflect.Value) (date time.Time, ok bool) { 288 | if data.Kind() == reflect.Map { 289 | for _, k := range data.MapKeys() { 290 | v := reflect.ValueOf(data.MapIndex(k).Interface()) 291 | if k.Kind() == reflect.String && k.String() == "$jsii.date" && v.Kind() == reflect.String { 292 | var err error 293 | date, err = time.Parse(time.RFC3339Nano, v.String()) 294 | ok = (err == nil) 295 | break 296 | } 297 | } 298 | } 299 | 300 | return 301 | } 302 | 303 | func castValToEnumRef(data reflect.Value) (enum api.EnumRef, ok bool) { 304 | ok = false 305 | 306 | if data.Kind() == reflect.Map { 307 | for _, k := range data.MapKeys() { 308 | // Finding values type requires extracting from reflect.Value 309 | // otherwise .Kind() returns `interface{}` 310 | v := reflect.ValueOf(data.MapIndex(k).Interface()) 311 | 312 | if k.Kind() == reflect.String && k.String() == "$jsii.enum" && v.Kind() == reflect.String { 313 | enum.MemberFQN = v.String() 314 | ok = true 315 | break 316 | } 317 | } 318 | } 319 | 320 | return 321 | } 322 | 323 | // castValToMap attempts converting the provided jsii wire value to a 324 | // go map. This recognizes the "$jsii.map" object and does the necessary 325 | // recursive value conversion. 326 | func (c *Client) castValToMap(data reflect.Value, mapType reflect.Type) (m reflect.Value, ok bool) { 327 | ok = false 328 | 329 | if data.Kind() != reflect.Map || data.Type().Key().Kind() != reflect.String { 330 | return 331 | } 332 | 333 | if mapType.Kind() == reflect.Map && mapType.Key().Kind() != reflect.String { 334 | return 335 | } 336 | if mapType == anyType { 337 | mapType = reflect.TypeOf((map[string]interface{})(nil)) 338 | } 339 | 340 | dataIter := data.MapRange() 341 | for dataIter.Next() { 342 | key := dataIter.Key().String() 343 | if key != "$jsii.map" { 344 | continue 345 | } 346 | 347 | // Finding value type requries extracting from reflect.Value 348 | // otherwise .Kind() returns `interface{}` 349 | val := reflect.ValueOf(dataIter.Value().Interface()) 350 | if val.Kind() != reflect.Map { 351 | return 352 | } 353 | 354 | ok = true 355 | 356 | m = reflect.MakeMap(mapType) 357 | 358 | iter := val.MapRange() 359 | for iter.Next() { 360 | val := iter.Value() 361 | // Note: reflect.New(t) returns a pointer to a newly allocated t 362 | convertedVal := reflect.New(mapType.Elem()).Elem() 363 | c.castAndSetToPtr(convertedVal, val) 364 | 365 | m.SetMapIndex(iter.Key(), convertedVal) 366 | } 367 | return 368 | } 369 | return 370 | } 371 | -------------------------------------------------------------------------------- /runtime/runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/aws/jsii-runtime-go/internal/api" 9 | "github.com/aws/jsii-runtime-go/internal/kernel" 10 | ) 11 | 12 | // FQN represents a fully-qualified type name in the jsii type system. 13 | type FQN api.FQN 14 | 15 | // Member is a runtime descriptor for a class or interface member 16 | type Member interface { 17 | toOverride() api.Override 18 | } 19 | 20 | // MemberMethod is a runtime descriptor for a class method (implementation of Member) 21 | type MemberMethod api.MethodOverride 22 | 23 | func (m MemberMethod) toOverride() api.Override { 24 | return api.MethodOverride(m) 25 | } 26 | 27 | // MemberProperty is a runtime descriptor for a class or interface property (implementation of Member) 28 | type MemberProperty api.PropertyOverride 29 | 30 | func (m MemberProperty) toOverride() api.Override { 31 | return api.PropertyOverride(m) 32 | } 33 | 34 | // Load ensures a npm package is loaded in the jsii kernel. 35 | func Load(name string, version string, tarball []byte) { 36 | c := kernel.GetClient() 37 | 38 | _, err := c.Load(kernel.LoadProps{ 39 | Name: name, 40 | Version: version, 41 | }, tarball) 42 | if err != nil { 43 | panic(err) 44 | } 45 | } 46 | 47 | // RegisterClass associates a class fully qualified name to the specified class 48 | // interface, member list, and proxy maker function. Panics if class is not a go 49 | // interface, or if the provided fqn was already used to register a different type. 50 | func RegisterClass(fqn FQN, class reflect.Type, members []Member, maker func() interface{}) { 51 | client := kernel.GetClient() 52 | 53 | overrides := make([]api.Override, len(members)) 54 | for i, m := range members { 55 | overrides[i] = m.toOverride() 56 | } 57 | 58 | if err := client.Types().RegisterClass(api.FQN(fqn), class, overrides, maker); err != nil { 59 | panic(err) 60 | } 61 | } 62 | 63 | // RegisterEnum associates an enum's fully qualified name to the specified enum 64 | // type, and members. Panics if enum is not a reflect.String type, any value in 65 | // the provided members map is of a type other than enum, or if the provided 66 | // fqn was already used to register a different type. 67 | func RegisterEnum(fqn FQN, enum reflect.Type, members map[string]interface{}) { 68 | client := kernel.GetClient() 69 | if err := client.Types().RegisterEnum(api.FQN(fqn), enum, members); err != nil { 70 | panic(err) 71 | } 72 | } 73 | 74 | // RegisterInterface associates an interface's fully qualified name to the 75 | // specified interface type, member list, and proxy maker function. Panics if iface is not 76 | // an interface, or if the provided fqn was already used to register a different type. 77 | func RegisterInterface(fqn FQN, iface reflect.Type, members []Member, maker func() interface{}) { 78 | client := kernel.GetClient() 79 | 80 | overrides := make([]api.Override, len(members)) 81 | for i, m := range members { 82 | overrides[i] = m.toOverride() 83 | } 84 | 85 | if err := client.Types().RegisterInterface(api.FQN(fqn), iface, overrides, maker); err != nil { 86 | panic(err) 87 | } 88 | } 89 | 90 | // RegisterStruct associates a struct's fully qualified name to the specified 91 | // struct type. Panics if strct is not a struct, or if the provided fqn was 92 | // already used to register a different type. 93 | func RegisterStruct(fqn FQN, strct reflect.Type) { 94 | client := kernel.GetClient() 95 | if err := client.Types().RegisterStruct(api.FQN(fqn), strct); err != nil { 96 | panic(err) 97 | } 98 | } 99 | 100 | // RegisterStructValidator adds a validator function to an already registered 101 | // struct type. This is separate call largely to maintain backwards compatibility 102 | // with existing code. 103 | func RegisterStructValidator(strct reflect.Type, validator func(interface{}, func() string) error) { 104 | client := kernel.GetClient() 105 | if err := client.Types().RegisterStructValidator(strct, validator); err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | // InitJsiiProxy initializes a jsii proxy instance at the provided pointer. 111 | // Panics if the pointer cannot be initialized to a proxy instance (i.e: the 112 | // element of it is not a registered jsii interface or class type). 113 | func InitJsiiProxy(ptr interface{}) { 114 | ptrVal := reflect.ValueOf(ptr).Elem() 115 | if err := kernel.GetClient().Types().InitJsiiProxy(ptrVal, ptrVal.Type()); err != nil { 116 | panic(err) 117 | } 118 | } 119 | 120 | // IsAnonymousProxy tells whether the value v is an anonymous object proxy, or 121 | // a pointer to one. 122 | func IsAnonymousProxy(v interface{}) bool { 123 | return kernel.GetClient().Types().IsAnonymousProxy(v) 124 | } 125 | 126 | // Create will construct a new JSII object within the kernel runtime. This is 127 | // called by jsii object constructors. 128 | func Create(fqn FQN, args []interface{}, inst interface{}) { 129 | client := kernel.GetClient() 130 | 131 | instVal := reflect.ValueOf(inst) 132 | structVal := instVal.Elem() 133 | instType := structVal.Type() 134 | numField := instType.NumField() 135 | for i := 0; i < numField; i++ { 136 | field := instType.Field(i) 137 | if !field.Anonymous { 138 | continue 139 | } 140 | switch field.Type.Kind() { 141 | case reflect.Interface: 142 | fieldVal := structVal.Field(i) 143 | if !fieldVal.IsNil() { 144 | continue 145 | } 146 | if err := client.Types().InitJsiiProxy(fieldVal, fieldVal.Type()); err != nil { 147 | panic(err) 148 | } 149 | 150 | case reflect.Struct: 151 | fieldVal := structVal.Field(i) 152 | if !fieldVal.IsZero() { 153 | continue 154 | } 155 | if err := client.Types().InitJsiiProxy(fieldVal, fieldVal.Type()); err != nil { 156 | panic(err) 157 | } 158 | } 159 | } 160 | 161 | // Find method overrides thru reflection 162 | mOverrides := getMethodOverrides(inst, "jsiiProxy_") 163 | // If overriding struct has no overriding methods, could happen if 164 | // overriding methods are not defined with pointer receiver. 165 | if len(mOverrides) == 0 && !strings.HasPrefix(instType.Name(), "jsiiProxy_") { 166 | panic(fmt.Errorf("%v has no overriding methods. Overriding methods must be defined with a pointer receiver", instType.Name())) 167 | } 168 | var overrides []api.Override 169 | registry := client.Types() 170 | added := make(map[string]bool) 171 | for _, name := range mOverrides { 172 | // Use getter's name even if setter is overriden 173 | if strings.HasPrefix(name, "Set") { 174 | propName := name[3:] 175 | if override, ok := registry.GetOverride(api.FQN(fqn), propName); ok { 176 | if !added[propName] { 177 | added[propName] = true 178 | overrides = append(overrides, override) 179 | } 180 | continue 181 | } 182 | } 183 | if override, ok := registry.GetOverride(api.FQN(fqn), name); ok { 184 | if !added[name] { 185 | added[name] = true 186 | overrides = append(overrides, override) 187 | } 188 | } 189 | } 190 | 191 | interfaces, newOverrides := client.Types().DiscoverImplementation(instType) 192 | overrides = append(overrides, newOverrides...) 193 | 194 | res, err := client.Create(kernel.CreateProps{ 195 | FQN: api.FQN(fqn), 196 | Arguments: convertArguments(args), 197 | Interfaces: interfaces, 198 | Overrides: overrides, 199 | }) 200 | 201 | if err != nil { 202 | panic(err) 203 | } 204 | 205 | if err = client.RegisterInstance(instVal, api.ObjectRef{InstanceID: res.InstanceID, Interfaces: interfaces}); err != nil { 206 | panic(err) 207 | } 208 | } 209 | 210 | // Invoke will call a method on a jsii class instance. The response will be 211 | // decoded into the expected return type for the method being called. 212 | func Invoke(obj interface{}, method string, args []interface{}, ret interface{}) { 213 | client := kernel.GetClient() 214 | 215 | // Find reference to class instance in client 216 | ref, found := client.FindObjectRef(reflect.ValueOf(obj)) 217 | 218 | if !found { 219 | panic("No Object Found") 220 | } 221 | 222 | res, err := client.Invoke(kernel.InvokeProps{ 223 | Method: method, 224 | Arguments: convertArguments(args), 225 | ObjRef: ref, 226 | }) 227 | 228 | if err != nil { 229 | panic(err) 230 | } 231 | 232 | client.CastAndSetToPtr(ret, res.Result) 233 | } 234 | 235 | // InvokeVoid will call a void method on a jsii class instance. 236 | func InvokeVoid(obj interface{}, method string, args []interface{}) { 237 | client := kernel.GetClient() 238 | 239 | // Find reference to class instance in client 240 | ref, found := client.FindObjectRef(reflect.ValueOf(obj)) 241 | 242 | if !found { 243 | panic("No Object Found") 244 | } 245 | 246 | _, err := client.Invoke(kernel.InvokeProps{ 247 | Method: method, 248 | Arguments: convertArguments(args), 249 | ObjRef: ref, 250 | }) 251 | 252 | if err != nil { 253 | panic(err) 254 | } 255 | } 256 | 257 | // StaticInvoke will call a static method on a given jsii class. The response 258 | // will be decoded into the expected return type for the method being called. 259 | func StaticInvoke(fqn FQN, method string, args []interface{}, ret interface{}) { 260 | client := kernel.GetClient() 261 | 262 | res, err := client.SInvoke(kernel.StaticInvokeProps{ 263 | FQN: api.FQN(fqn), 264 | Method: method, 265 | Arguments: convertArguments(args), 266 | }) 267 | 268 | if err != nil { 269 | panic(err) 270 | } 271 | 272 | client.CastAndSetToPtr(ret, res.Result) 273 | } 274 | 275 | // StaticInvokeVoid will call a static void method on a given jsii class. 276 | func StaticInvokeVoid(fqn FQN, method string, args []interface{}) { 277 | client := kernel.GetClient() 278 | 279 | _, err := client.SInvoke(kernel.StaticInvokeProps{ 280 | FQN: api.FQN(fqn), 281 | Method: method, 282 | Arguments: convertArguments(args), 283 | }) 284 | 285 | if err != nil { 286 | panic(err) 287 | } 288 | } 289 | 290 | // Get reads a property value on a given jsii class instance. The response 291 | // should be decoded into the expected type of the property being read. 292 | func Get(obj interface{}, property string, ret interface{}) { 293 | client := kernel.GetClient() 294 | 295 | // Find reference to class instance in client 296 | ref, found := client.FindObjectRef(reflect.ValueOf(obj)) 297 | 298 | if !found { 299 | panic(fmt.Errorf("no object reference found for %v", obj)) 300 | } 301 | 302 | res, err := client.Get(kernel.GetProps{ 303 | Property: property, 304 | ObjRef: ref, 305 | }) 306 | 307 | if err != nil { 308 | panic(err) 309 | } 310 | 311 | client.CastAndSetToPtr(ret, res.Value) 312 | } 313 | 314 | // StaticGet reads a static property value on a given jsii class. The response 315 | // should be decoded into the expected type of the property being read. 316 | func StaticGet(fqn FQN, property string, ret interface{}) { 317 | client := kernel.GetClient() 318 | 319 | res, err := client.SGet(kernel.StaticGetProps{ 320 | FQN: api.FQN(fqn), 321 | Property: property, 322 | }) 323 | 324 | if err != nil { 325 | panic(err) 326 | } 327 | 328 | client.CastAndSetToPtr(ret, res.Value) 329 | } 330 | 331 | // Set writes a property on a given jsii class instance. The value should match 332 | // the type of the property being written, or the jsii kernel will crash. 333 | func Set(obj interface{}, property string, value interface{}) { 334 | client := kernel.GetClient() 335 | 336 | // Find reference to class instance in client 337 | ref, found := client.FindObjectRef(reflect.ValueOf(obj)) 338 | 339 | if !found { 340 | panic("No Object Found") 341 | } 342 | 343 | _, err := client.Set(kernel.SetProps{ 344 | Property: property, 345 | Value: client.CastPtrToRef(reflect.ValueOf(value)), 346 | ObjRef: ref, 347 | }) 348 | 349 | if err != nil { 350 | panic(err) 351 | } 352 | } 353 | 354 | // StaticSet writes a static property on a given jsii class. The value should 355 | // match the type of the property being written, or the jsii kernel will crash. 356 | func StaticSet(fqn FQN, property string, value interface{}) { 357 | client := kernel.GetClient() 358 | 359 | _, err := client.SSet(kernel.StaticSetProps{ 360 | FQN: api.FQN(fqn), 361 | Property: property, 362 | Value: client.CastPtrToRef(reflect.ValueOf(value)), 363 | }) 364 | 365 | if err != nil { 366 | panic(err) 367 | } 368 | } 369 | 370 | // convertArguments turns an argument struct and produces a list of values 371 | // ready for inclusion in an invoke or create request. 372 | func convertArguments(args []interface{}) []interface{} { 373 | if len(args) == 0 { 374 | return nil 375 | } 376 | 377 | result := make([]interface{}, len(args)) 378 | client := kernel.GetClient() 379 | for i, arg := range args { 380 | val := reflect.ValueOf(arg) 381 | result[i] = client.CastPtrToRef(val) 382 | } 383 | 384 | return result 385 | } 386 | 387 | // Get ptr's methods names which override "base" struct methods. 388 | // The "base" struct is identified by name prefix "basePrefix". 389 | func getMethodOverrides(ptr interface{}, basePrefix string) (methods []string) { 390 | // Methods override cache: [methodName]bool 391 | mCache := make(map[string]bool) 392 | getMethodOverridesRec(ptr, basePrefix, mCache) 393 | // Return overriden methods names in embedding hierarchy 394 | for m := range mCache { 395 | methods = append(methods, m) 396 | } 397 | return 398 | } 399 | 400 | func getMethodOverridesRec(ptr interface{}, basePrefix string, cache map[string]bool) { 401 | ptrType := reflect.TypeOf(ptr) 402 | if ptrType.Kind() != reflect.Ptr { 403 | return 404 | } 405 | structType := ptrType.Elem() 406 | if structType.Kind() != reflect.Struct { 407 | return 408 | } 409 | if strings.HasPrefix(structType.Name(), basePrefix) { 410 | // Skip base class 411 | return 412 | } 413 | 414 | ptrVal := reflect.ValueOf(ptr) 415 | structVal := ptrVal.Elem() 416 | 417 | // Add embedded/super overrides first 418 | for i := 0; i < structType.NumField(); i++ { 419 | field := structType.Field(i) 420 | if !field.Anonymous { 421 | continue 422 | } 423 | if field.Type.Kind() == reflect.Ptr || 424 | field.Type.Kind() == reflect.Interface { 425 | p := structVal.Field(i) 426 | if !p.IsNil() { 427 | getMethodOverridesRec(p.Interface(), basePrefix, cache) 428 | } 429 | } 430 | } 431 | // Add overrides in current struct 432 | // Current struct's value-type method-set 433 | valMethods := make(map[string]bool) 434 | for i := 0; i < structType.NumMethod(); i++ { 435 | valMethods[structType.Method(i).Name] = true 436 | } 437 | // Compare current struct's pointer-type method-set to its value-type method-set 438 | for i := 0; i < ptrType.NumMethod(); i++ { 439 | mn := ptrType.Method(i).Name 440 | if !valMethods[mn] { 441 | cache[mn] = true 442 | } 443 | } 444 | } 445 | --------------------------------------------------------------------------------