Go¶
Code in Go defines func Main(g *am.G) (any, error) in a package which imports apimaker/am. It is built once as a plugin of the runner, on the official image of Go (golang:1.27 unless the settings name another version), with only the modules you add.
In short
- Write
func Main(g *am.G) (any, error)in a package that importsapimaker/am. - It is built once, on
golang:1.27unless the settings name another version. - Refuse a request with
return nil, am.Fail(400, message).
| hello.go | |
|---|---|
| Runtime | Go 1.21 or later, a version of golang:<version> |
Main |
func Main(g *am.G) (any, error) : what it returns is the answer, an error refuses |
Calls of g.Sys |
typed : what a call takes is a struct (am.GetAll{…}), what it answers has its type ([]am.M, int, am.UpdateManyResult) |
| Values | am.M (map[string]any) for objects and rows, []any for lists, float64 for numbers |
| Package | any name, package code in a new item : the sandbox builds it as a plugin whatever it is named. In git every piece of code is a package of its own, in a folder of its own |
| Packages | Go modules : github.com/google/uuid v1.6.0 |
| Printing | fmt.Println and log go to the console of the account |
The request and the answer¶
g.Req and g.Res have a method for each key they are known to have : g.Req.Body(), g.Req.Query(), g.Req.Params(), g.Req.Headers(), g.Res.SetStatusCode(201). What they give is an am.M : Map, List, String, Float, Int and Bool read a key without a type assertion and give the zero value when it is not there. The map is the one inside the request, so changing it changes the request.
A body which is a list ([{…}, {…}], several rows to save) is read with g.Req.List("body") ; g.Req.Body() is an empty am.M then.
Bytes as the answer¶
A file, a picture, a PDF : return its bytes (a []byte), or give them to g.Res.SetOutput, and name their kind of content. The answer is those bytes as they are, whatever the kind of content, with the headers of the code. (A text under a kind of bytes, image/gif, is read as base64.)
Calling API Maker : g.Sys¶
Every call has its types. What it takes is a struct of am whose fields are its keys (am.GetAll, am.GetById, am.Count…) ; what it answers is typed too : rows are a []am.M, a row an am.M (nil when there is none), a count an int, a key of the cache a *string (nil when it is not there), what an API describes a struct (am.UpdateManyResult).
- A key which is left out is not sent. A flag which is on by default is a pointer, so
falsecan be said :NoCycle: am.Ptr(false). - A key the struct has no field for goes in
Other:am.GetAll{Instance: "mysql_8", Other: am.M{"cursor": "abc"}}. - A row as a struct of your own :
customer, err := am.To[Customer](rows[0]).
Calls at the same time are goroutines :
Rows as they arrive¶
| Sum a large table | |
|---|---|
System APIs and cache¶
Errors¶
Return am.Fail(code, message) to refuse, or an *am.Errors with several errors :
| Refuse a request | |
|---|---|
A call which fails gives an *am.Errors with the errors of the server :
- Another error answers
500with its message. A panic answers500withpanic: …and the lines of your code (at orders.go:14 in helper()). - A panic in a goroutine you started ends the runner : recover inside your goroutines.
Logs, printing and shared values¶
Hooks¶
| Pre hook : only my rows | |
|---|---|
| Post hook : hide a column | |
|---|---|
Utility classes¶
A utility class written in Go is a package. The other Go code of the account imports it by the folder it has in the Git repository, which is the module apimaker :
package tax
import round "apimaker/src/utility_classes/utils---Round" // another utility class
var Rate = 0.2
func Add(amount float64) float64 {
return round.Cents(amount * (1 + Rate))
}
package code
import (
"apimaker/am"
tax "apimaker/src/utility_classes/utils---Tax"
)
func Main(g *am.G) (any, error) {
return am.M{"total": tax.Add(g.Req.Body().Float("amount"))}, nil
}
- The folder of a class is
src/utility_classes/<folder>---<name>(utils---Taxfor the classTaxinutils/) : letters of a to z, digits,.,-and_. The name afterpackageis yours; name the import as you like. - The package is built once and shared by all Go code of the account in a sandbox : what it keeps in a variable is kept there. A class which is saved again is another build, which the code gets when it is compiled again on its next run.
go build ./...in the repository builds the same imports : nothing else is needed.
Test cases¶
A test case written in Go lists its tests in Tests, in the place of a Main. A test is a func(g *am.G) error : the error it returns, or what it panics with, is its failure.
package code
import (
"fmt"
"apimaker/am"
)
func theCostOfAParcel(g *am.G) error {
if err := g.Sys.Test.Mock(am.Mock{Method: "db.getAll", When: am.M{"collection": "rates"}, Return: []any{am.M{"per_kg": 2.5}}}); err != nil {
return err
}
out, err := g.Sys.Test.RunCustomApi(am.RunCustomApi{Name: "shippingCost", Body: am.M{"weight": 4}})
if err != nil {
return err
}
if cost := am.AsM(out).Float("cost"); cost != 10 {
return fmt.Errorf("the cost is %v", cost)
}
return nil
}
var Tests = []am.Test{
{Name: "the cost of a parcel", Run: theCostOfAParcel},
}
g.Sys.Test:RunCustomApi(am.RunCustomApi{…}),RunCustomApiFull(…)(the whole answer :Data,StatusCode,Headers,Logs),Mock(am.Mock{…}),Mocks([]am.Mock{…}),ClearMocks(method...),Calls(method...).- The custom API under test can be written in any language.
Packages¶
Add github.com/google/uuid in Sandbox Settings, then :
| A Go module | |
|---|---|
- A version without
vgets one :1.6.0isv1.6.0; empty islatest. - Modules are downloaded when the image is built; while code runs nothing is downloaded (
GOPROXY=off).
How it runs¶
- Each piece of code is built once as a plugin of the runner (about a second), from the same module as the runner : the packages of the account and
apimaker/amare shared. Saving starts the build in the background; code which does not compile is reported in the console of the account (main.go:6:9: undefined: missing). - Every request is a goroutine; requests run at the same time, on four threads of Go at most. Code of an API waits for its database most of the time, and Go wakes an idle thread for every goroutine which becomes ready : with a thread for every core of a big machine that costs more than the request. Code which computes on many cores sets
GOMAXPROCSto the number it wants, with anENVline of the Dockerfile of Go :ENV GOMAXPROCS=16. - Variables at package level live as long as the runner : protect them with a mutex, or use
g.Shared.
In the code editor¶
The editor completes g as Go writes it, the names of your instances, databases and tables, and the columns of a table on the rows a call answers. More in the code editor in every language.
am.offers what the package has : the structs of what the calls take (am.GetAll,am.Save…),am.M,am.Fail,am.To. Between the braces of such a struct the editor offers its fields, each with what it is for ; insideFind: am.M{"the columns of the table, insideSelect: "andSort: "too.- What a call answers has its type : on the rows of a
GetAllthe columns are offered inside the quotes ofGet,Map,List,String,Float,IntandBool, and of an index (rows[0][") ;changed.offersUpdatedRowsCount,saved.offersRow,RowsandIsList,whole.(a call ofg.Sys.Db.Full)Success,Data,Errors… g.Req.andg.Res.offer a method for each key of the request and of the answer ;g.Req.Auth().the opened tokens.
Reference¶
g. |
Go |
|---|---|
Req |
Body(), Query(), Params(), Headers(), Auth(), ReqInfo() (each an am.M), EventData(), IsApiRequestFromUser(), and Set… for each |
Res |
SetStatusCode, SetContentType, Headers(), Shared(), Output(), SetOutput, Errors(), Warnings() |
an am.M |
Get, Map, List, String, Float, Int, Bool |
Sys.Db |
GetAll(am.GetAll), GetById(am.GetById), Query(am.Query), SaveSingleOrMultiple(am.Save), MasterSave(am.Save), ArrayOperations(am.ArrayOperations), UpdateById(am.UpdateById), UpdateMany(am.UpdateMany), ReplaceById(am.UpdateById), RemoveById(am.RemoveById), RemoveByQuery(am.RemoveByQuery), Aggregate(am.Aggregate), Count(am.Count), Distinct(am.Distinct), DistinctQuery(am.Distinct), GetAllByStream, QueryByStream; Sys.Db.Gen.GetAllGen… |
Sys.Db.Full, Sys.Db.Gen.Full, Sys.System.Full, Sys.Cache.Full |
the same calls, each with its whole answer : am.ApiResponse[T] (Success, StatusCode, Data, TotalCount, Errors, Warnings) |
Sys.System |
Encrypt, EncryptWith, Decrypt, DecryptWith, Hash, GetSecret, GetSecrets, CallExternalApi, CallExternalApis, GetToken, GetTokens, ExecuteQuery, GetTableMeta, GetTableMetas, CreateIndexes, DropIndexes, DropIndexesOfMany, GetIndexes, GetIndexesOfMany, EmitEvent, EmitEventWS, IsValidDataForTable(s), IsValidDataForCustomAPI(s), MultiTenantInstance(s)Updated, IsValidConnectionString(s), GetLock, ReleaseLock |
Sys.Cache |
GetKey, GetKeys, SetKey, SetKeys, RemoveKey, RemoveKeys, ResetCacheDB, ResetCacheDBs, ResetCacheCustomApis, ResetCacheCustomApisOfTenant, ResetCacheSystemApis, ResetCacheSystemApisOfTenant |
Sys.Call("db.getAll", …) |
any API by its name in JavaScript, with its arguments as they are |
Logger |
Log, Debug, Info, Warn, Error |
Shared |
Get, Set, Update |
| errors | am.Fail(code, message), &am.Errors{List: []am.M{…}} |
Debugging¶
- The debug runner of Go builds your code without optimizations and runs under Delve : the Debug tab of the API testing page steps through it.
- A panic stops the code when Pause on exceptions is on.
- Code on the native process of a Mac needs the developer mode of the Mac (
sudo DevToolsSecurity -enable) : Delve works through the debugserver of Xcode. - Learn more Debugging.