To be able to run the tests, you need to have test credentials. To obtain test credentials, contact us.
These credentials must be put in a test_credentials.json
file, in the root of this repository.
You can find a test_credentials.template.json
that shows the expected format.
To build on iOS, in order to avoid random crashes of the SDK (unaligned arguments
errors), you need to build a custom
Go toolchain, which includes the patch https://go-review.googlesource.com/c/go/+/408395.
To build this custom toolchain, you must:
- Pull the golang project ( https://github.com/golang/go/ ) and checkout the correct version (at the time of writing, the
go1.21.5
tag) - Modify 2 files:
src/cmd/cgo/out.go
with the same change as in the PR https://go-review.googlesource.com/c/go/+/408395 : adding__attribute__((aligned(8)))
(warning, the line is not exactly the same as in the PR, it is ~10 lines further)src/cmd/go/internal/version/version.go
to addcustom-seald-build
on line 81 at the end of the template string (this adds it in the output ofgo version
, which is checked in CI to verify that we are using a custom toolchain)
- Go in
src
and run./all.bash
to build the go toolchain - Add the path written at the end of the output of the previous command at the beginning of your PATH (warning, for it to be taken into account by the gitlab runner, it must be added to
~/.profile
, not only~/.zprofile
)
To publish releases of the SDK to Maven Central (Android), Cocoapods (iOS), and pub.dev (Flutter), you must push a tag to the repo. The CI will take care of the rest.
The tags must be semver valid version numbers.
Code must be formatted by gofmt
.
File names must be in snake_case
.
Exposed functions must return either no results, one result, or two results where the type of the second is the built-in 'error' type.
When an exposed function returns a custom type, this type can only be a struct. golang/go#27297
Package names cannot collide with struct names. macOS will have troubles with case-insensitive path...
The State
has its own zerolog logger instance in state.Logger
. Each API has a dedicated sub-logger.
Available log levels:
- panic
- fatal
- error
- warn
- info
- debug
- trace
To log a message:
Get the instance logger, choose a log level, and use the Msg()
function:
logger.Debug().Msg("My debug log")
To add contextual fields to a log message:
- Add a string:
logger.Trace().Str("str to log", strToLog).Msg("Trace level log message")
- Add a struct
logger.Info().Interface("struc name", myStruct).Msg("Info level log message")
When a function you call returns an error, and you want to pass it along,
you must use tracerr.Wrap
:
package myPackage
import "github.com/ztrue/tracerr"
func myFunc() (*ReturnType, error) {
err := FunctionWhichCanReturnError()
if err != nil {
return nil, tracerr.Wrap(err)
}
// ...
}
When you need to create a new error, you must not create it inside your function code.
Instead, you must create it in global scope, with utils.NewSealdError
and a unique error message,
and store it in an exported var
, so that anyone using the function can compare the error they receive to it.
When there are multiple errors in the same file, they must all be in a single var
declaration,
with parenthesis.
Additionally, your error must have a very specific unique name and error message, potentially with the function name which can return it inside.
When you need to return the error in question, you must use tracerr.Wrap
.
You can either wrap the error directly, or use .AddDetails("details to add")
to add details to the error.
Also, do not forget the errors' GoDoc.
package myPackage
import (
"github.com/ztrue/tracerr"
"github.com/seald/go-seald-sdk/utils"
)
var (
// ErrorName is returned when condition1
ErrorName = utils.NewSealdError("unique error message")
// ErrorMyFuncErrorType is returned when condition2
ErrorMyFuncErrorType = utils.NewSealdError("other unique error message")
)
func myFunc() (*ReturnType, error) {
if condition1 {
return nil, tracerr.Wrap(ErrorName)
}
if condition2 {
return nil, tracerr.Wrap(ErrorMyFuncErrorType.AddDetails("my details to add"))
}
// ...
return nil, nil
}
For comparing errors, you should use errors.Is
.
For SealdErrors, you should compare them to the exported error in question.
package myPackage
import (
"errors"
"github.com/ztrue/tracerr"
"github.com/seald/go-seald-sdk/utils"
)
func otherFunc() error {
_, err = myFunc()
if err != nil {
if errors.Is(err, ErrorMyFuncErrorType) {
// Do something specific
}
return err
}
return nil
}
For API Errors, you should compare them to a newly created APIError
instance, with Status and Code set.
package myPackage
import (
"errors"
"github.com/ztrue/tracerr"
"github.com/seald/go-seald-sdk/utils"
)
func otherFunc() error {
_, err = apiStuff()
if err != nil {
if errors.Is(err, utils.APIError{Status: 406, Code: "MY_ERROR_CODE"}) {
// Do something specific
}
return err
}
return nil
}
You may also want to cast the retrieved error as a specific type (either utils.SealdError
or utils.APIError
),
with errors.As
.
You should rarely have to do this. You probably want to use errors.Is
instead.
package myPackage
import (
"errors"
"github.com/ztrue/tracerr"
"github.com/seald/go-seald-sdk/utils"
)
func otherFunc() error {
_, err = apiStuff()
if err != nil {
var apiError utils.APIError
if errors.As(err, &apiError) { // casting the error as an `APIError`
// you can then use `apiError` as an instance of a`APIError`
} else {
// `err` is not an `APIError`
}
return err
}
return nil
}
Every exported symbol must have GoDoc comments. Official guidelines: https://go.dev/doc/comment.
The GoDoc comment must start with //
, followed by a space.
It must be a complete sentence, starting with a capital letter and ending with a point.
All doc comments must start by explicitly stating the name of the symbol they are documenting,
possibly prefixed by an article.
In doc comments, you can refer to argument names, or other exported symbols, directly by name, without back quotes or any special syntax. You may also want to name the return types to refer to them.
Example:
package myPackage
import (
"errors"
)
var (
// ErrorName is returned when there is an error in MyFunc.
ErrorName = errors.New("error message")
)
// MyReturnType is the type that MyFunc returns.
type MyReturnType struct {
// You can also document type fields, but that is not mandatory,
// as long as the field meaning is sufficiently self-explanatory,
// or explained in the type's doc comment.
MyField string
}
// The MyFunc function does stuff. It takes myArg, and will return myReturnInstance, a pointer to MyReturnType, or myError.
func MyFunc(myArg string) (myReturnInstance *MyReturnType, myError error) {
v := MyReturnType{MyField: myArg}
return &v, nil
}
When implementing new features in GoLang, they should be tested, both against the GoLang SDK itself, and if applicable against the JS SDK
When implementing tests in GoLang, you should test both the normal cases, and the error cases, as much as possible. You should strive for 100% coverage if possible. The only lines which are acceptable to not tests are those that simply test for an error and re-throw it, if the error in question is otherwise tested.
In tests, you should use require.NoError(t, err)
to check for errors, instead of an if
block,
for the sake of readability and consistency.
Also, you should use t.Parallel()
everywhere possible, to reduce the duration of tests.
To test the GoLang implementation against the JS SDK, you may need to execute JS code both before and after your GoLang tests. A common pattern is to create artifacts in JS, then use them in Go to verify than the Go implementation is compatible with them, then create other artifacts in Go, and use them in JS to verify the other way around.
To do so, for example for a module named my_module
,
you should create a mocha JS test file in tests_js/before_go/my_module.spec.js
,
which creates the necessary artifacts in test_artifacts/from_js/my_module/
.
Artifacts must be named in snake_case
.
After that, you can implement the necessary tests in GoLang in your my_module_test.go
file.
This file should both test the artifacts generated by the JS, and generate new artifacts from GoLang for the JS to test.
These new artifacts must be in test_artifacts/from_go/my_module/
, and follow the same rules as previously.
You may want to use the following pattern:
package my_module
import (
"github.com/stretchr/testify/require"
"github.com/seald/go-seald-sdk/test_utils"
"os"
"path/filepath"
"testing"
)
func Test_MyModule(t *testing.T) {
t.Parallel()
// Other tests ...
t.Run("Compatible with JS", func(t *testing.T) {
t.Parallel()
t.Run("Import from JS", func(t *testing.T) {
testArtifactsDir := filepath.Join(test_utils.GetCurrentPath(), "../test_artifacts/from_js/my_module")
// Reading an artifact
myArtifact, err := os.ReadFile(filepath.Join(testArtifactsDir, "my_artifact"))
require.NoError(t, err)
// implement Go tests for the JS artifacts
err = TestArtifact(myArtifact)
require.NoError(t, err)
})
t.Run("Export for JS", func(t *testing.T) {
// ensure artifacts dir exists
testArtifactsDir := filepath.Join(test_utils.GetCurrentPath(), "../test_artifacts/from_go/my_module")
err := os.MkdirAll(testArtifactsDir, 0700)
require.NoError(t, err)
// implement Go tests to write artifacts
myArtifact := GenerateArtifactSomehow()
err = os.WriteFile(filepath.Join(testArtifactsDir, "my_artifact"), myArtifact, 0o700)
})
})
}
Lastly, you must create a mocha JS test file in tests_js/after_go/my_module.spec.js
file,
which reads the artifacts created by the GoLang test and tests them against the JS SDK.
In general, the JS test in before_go
will follow the same logic as the GoLang test in Export for JS
,
and the JS test in after_go
will follow the GoLang test in Import from JS
.
Also, the JS and GoLang should generate the same artifacts.