From 61d56f0308704d36dafaa1ad970fe54fc571b078 Mon Sep 17 00:00:00 2001 From: Junyi Yi Date: Thu, 14 Nov 2024 12:50:26 -0500 Subject: [PATCH] Simplify the native code by introducing GoPlugin --- client/go/outline/electron/cstring.go | 39 -------- client/go/outline/electron/fetch.go | 50 --------- client/go/outline/electron/go_plugin.go | 128 ++++++++++++++++++++++++ client/go/outline/electron/init.go | 39 -------- client/go/outline/electron/platerr.go | 56 ----------- client/go/outline/electron/platerr.h | 29 ------ 6 files changed, 128 insertions(+), 213 deletions(-) delete mode 100644 client/go/outline/electron/cstring.go delete mode 100644 client/go/outline/electron/fetch.go create mode 100644 client/go/outline/electron/go_plugin.go delete mode 100644 client/go/outline/electron/init.go delete mode 100644 client/go/outline/electron/platerr.go delete mode 100644 client/go/outline/electron/platerr.h diff --git a/client/go/outline/electron/cstring.go b/client/go/outline/electron/cstring.go deleted file mode 100644 index 2096752f04..0000000000 --- a/client/go/outline/electron/cstring.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -// #include -import "C" -import ( - "log/slog" - "unsafe" -) - -// NewCGoString allocates memory for a C string based on the given Go string. -// It should be paired with [FreeCGoString] to avoid memory leaks. -func NewCGoString(s string) *C.char { - res := C.CString(s) - slog.Debug("malloc CGoString", "addr", res) - return res -} - -// FreeCGoString releases the memory allocated by NewCGoString. -// It also accepts null. -// -//export FreeCGoString -func FreeCGoString(s *C.char) { - slog.Debug("free CGoString", "addr", s) - C.free(unsafe.Pointer(s)) -} diff --git a/client/go/outline/electron/fetch.go b/client/go/outline/electron/fetch.go deleted file mode 100644 index 8be793a50b..0000000000 --- a/client/go/outline/electron/fetch.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -/* -#include "platerr.h" - -// FetchResourceResult represents the result of fetching a resource located at a URL. -typedef struct t_FetchResourceResult { - // The content of the fetched resource. - // Caller is responsible for freeing this pointer using FreeCGoString. - const char *Content; - - // If this is not null, it represents the error encountered during fetching. - // Caller is responsible for freeing this pointer using FreeCGoPlatformError. - const PlatformError *Error; -} FetchResourceResult; -*/ -import "C" -import "github.com/Jigsaw-Code/outline-apps/client/go/outline" - -// FetchResource fetches a resource located at the given URL. -// -// The function returns a C FetchResourceResult containing the Content of the resource -// and any Error encountered during fetching. -// -// You don't need to free the memory of FetchResourceResult struct itself, as it's not a pointer. -// However, you are responsible for freeing the memory of its Content and Error fields. -// -//export FetchResource -func FetchResource(cstr *C.char) C.FetchResourceResult { - url := C.GoString(cstr) - result := outline.FetchResource(url) - return C.FetchResourceResult{ - Content: NewCGoString(result.Content), - Error: ToCGoPlatformError(result.Error), - } -} diff --git a/client/go/outline/electron/go_plugin.go b/client/go/outline/electron/go_plugin.go new file mode 100644 index 0000000000..23f38702f1 --- /dev/null +++ b/client/go/outline/electron/go_plugin.go @@ -0,0 +1,128 @@ +// Copyright 2024 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +/* +#include // for C.free + +// InvokeGoAPIResult is a struct used to pass result from Go to TypeScript boundary. +typedef struct InvokeGoAPIResult_t +{ + // A string representing the result of the Go function call. + // This may be a raw string or a JSON string depending on the API call. + const char *Output; + + // A string containing a JSON representation of any error that occurred during the + // Go function call, or NULL if no error occurred. + // This error can be parsed by the PlatformError in TypeScript. + const char *ErrorJson; +} InvokeGoAPIResult; +*/ +import "C" +import ( + "fmt" + "log/slog" + "os" + "unsafe" + + "github.com/Jigsaw-Code/outline-apps/client/go/outline" + "github.com/Jigsaw-Code/outline-apps/client/go/outline/platerrors" +) + +// API name constants +const ( + // FetchResourceAPI fetches a resource located at a given URL. + // + // - Input: the URL string of the resource to fetch + // - Output: the content in raw string of the fetched resource + FetchResourceAPI = "FetchResource" +) + +// InvokeGoFunc is the unified entry point for TypeScript to invoke various Go functions. +// +// The input and output are all defined as string, but they may represent either a raw string, +// or a JSON string depending on the API call. +// +// Check the API name constants comment for more details about the input and output format. +// +//export InvokeGoAPI +func InvokeGoAPI(api *C.char, input *C.char) C.InvokeGoAPIResult { + apiName := C.GoString(api) + switch apiName { + + case FetchResourceAPI: + res := outline.FetchResource(C.GoString(input)) + return C.InvokeGoAPIResult{ + Output: newCGoString(res.Content), + ErrorJson: marshalCGoErrorJson(platerrors.ToPlatformError(res.Error)), + } + + default: + err := &platerrors.PlatformError{ + Code: platerrors.IllegalConfig, + Message: fmt.Sprintf("unsupported Go API: %s", apiName), + } + return C.InvokeGoAPIResult{ErrorJson: marshalCGoErrorJson(err)} + } +} + +// newCGoString allocates memory for a C string based on the given Go string. +// It should be paired with [FreeCGoString] to avoid memory leaks. +func newCGoString(s string) *C.char { + res := C.CString(s) + slog.Debug("malloc CGoString", "addr", res) + return res +} + +// FreeCGoString releases the memory allocated by newCGoString. +// It also accepts null. +// +//export FreeCGoString +func FreeCGoString(s *C.char) { + slog.Debug("free CGoString", "addr", s) + C.free(unsafe.Pointer(s)) +} + +// marshalCGoErrorJson marshals a PlatformError to a C style JSON string. +// It always succeeds with a non-empty string if e is not nil. +func marshalCGoErrorJson(e *platerrors.PlatformError) *C.char { + if e == nil { + return nil + } + json, err := platerrors.MarshalJSONString(e) + if err != nil { + return newCGoString(fmt.Sprintf("%s, failed to retrieve details due to: %s", e.Code, err.Error())) + } + return newCGoString(json) +} + +// init initializes the backend module. +// It sets up a default logger based on the OUTLINE_DEBUG environment variable. +func init() { + opts := slog.HandlerOptions{Level: slog.LevelInfo} + + dbg := os.Getenv("OUTLINE_DEBUG") + if dbg != "" && dbg != "false" { + dbg = "true" + opts.Level = slog.LevelDebug + } else { + dbg = "false" + } + + logger := slog.New(slog.NewTextHandler(os.Stderr, &opts)) + slog.SetDefault(logger) + + slog.Info("Backend module initialized", "debug", dbg) +} diff --git a/client/go/outline/electron/init.go b/client/go/outline/electron/init.go deleted file mode 100644 index 63268e67bc..0000000000 --- a/client/go/outline/electron/init.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "log/slog" - "os" -) - -// init initializes the backend module. -// It sets up a default logger based on the OUTLINE_DEBUG environment variable. -func init() { - opts := slog.HandlerOptions{Level: slog.LevelInfo} - - dbg := os.Getenv("OUTLINE_DEBUG") - if dbg != "" && dbg != "false" { - dbg = "true" - opts.Level = slog.LevelDebug - } else { - dbg = "false" - } - - logger := slog.New(slog.NewTextHandler(os.Stderr, &opts)) - slog.SetDefault(logger) - - slog.Info("Backend module initialized", "debug", dbg) -} diff --git a/client/go/outline/electron/platerr.go b/client/go/outline/electron/platerr.go deleted file mode 100644 index b3242c344a..0000000000 --- a/client/go/outline/electron/platerr.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -/* -#include -#include "platerr.h" -*/ -import "C" - -import ( - "fmt" - "log/slog" - "unsafe" - - "github.com/Jigsaw-Code/outline-apps/client/go/outline/platerrors" -) - -// ToCGoPlatformError allocates memory for a C PlatformError if the given Go PlatformError is not nil. -// It should be paired with [FreeCGoPlatformError] to avoid memory leaks. -func ToCGoPlatformError(e *platerrors.PlatformError) *C.PlatformError { - if e == nil { - return nil - } - json, err := platerrors.MarshalJSONString(e) - if err != nil { - json = fmt.Sprintf("%s, failed to retrieve details due to: %s", e.Code, err.Error()) - } - - res := (*C.PlatformError)(C.malloc(C.sizeof_PlatformError)) - slog.Debug("malloc CGoPlatformError", "addr", unsafe.Pointer(res)) - res.Code = NewCGoString(e.Code) - res.DetailJson = NewCGoString(json) - return res -} - -// FreeCGoPlatformError releases the memory allocated by ToCGoPlatformError. -// It also accepts null. -// -//export FreeCGoPlatformError -func FreeCGoPlatformError(e *C.PlatformError) { - slog.Debug("free CGoPlatformError", "addr", unsafe.Pointer(e)) - C.free(unsafe.Pointer(e)) -} diff --git a/client/go/outline/electron/platerr.h b/client/go/outline/electron/platerr.h deleted file mode 100644 index 01e8175e2a..0000000000 --- a/client/go/outline/electron/platerr.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2024 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -// PlatformError represents an error that originate from the native network code. -typedef struct t_PlatformError -{ - - // A code can be used to identify the specific type of the error. - // Caller is responsible for freeing this pointer using FreeCGoString. - const char *Code; - - // A JSON string of the error details that can be parsed by TypeScript. - // Caller is responsible for freeing this pointer using FreeCGoString. - const char *DetailJson; - -} PlatformError;