Documentation
¶
Overview ¶
Package play provides callable APIs and HTTP handlers to format, run, and test Go code, similar to Go playground but using HTTP instead of WebSocket.
For HTTP API, this package expose handlers: [HTTPHandleFormat], [HTTPHandleRun], and [HTTPHandleTest].
Formatting and running Go code ¶
The HTTP APIs for formatting and running Go code accept the following JSON request format,
{ "goversion": <string>, // For run only. "without_race": <boolean>, // For run only. "body": <string> }
The "goversion" field define the Go tools and toolchain version to be used to compile the code. The default "goversion" is defined in global variable GoVersion in this package. If "without_race" is true, the Run command will not run with "-race" option. The "body" field contains the Go code to be formatted or run.
Both request return the following JSON response format,
{ "code": <integer, HTTP status code>, "name": <string, error type>, "message": <string, optional message>, "data": <string> }
For the Go.HTTPHandleFormat, the response "data" contains the formatted Go code. For the Go.HTTPHandleRun, the response "data" contains the output from running the Go code. The "message" field contains an error on pre-Run, like bad request or file system related error.
Unsafe run ¶
As exceptional, the Go.Run and Go.HTTPHandleRun accept the following request for running program inside custom "go.mod",
{ "unsafe_run": <path> }
The "unsafe_run" define the path to directory relative to play.GoOptions.Root working directory. Once the request is accepted it will change the directory into "unsafe_run" first and then execute "go run ." directly. Go code that executed inside "unsafe_run" should be not modifiable and safe from mallicious execution.
Testing ¶
The Go.Test or Go.HTTPHandleTest must run inside the directory that contains the Go code file to be tested. The Go.HTTPHandleTest API accept the following request format,
{ "goversion": <string>, "file": <string>, "body": <string>, "without_race": <boolean> }
The "file" field define the path to the "_test.go" file, default to "test_test.go" if its empty. The "body" field contains the Go code that will be saved to that "file". The test will run, by default, with "go test -count=1 -race $dirname" where "$dirname" is the path to directory where "file" located, must be under the play.GoOptions.Root directory. If "without_race" is true, the test command will not run with "-race" option.
Index ¶
- Constants
- Variables
- type Go
- func (playgo *Go) Format(req Request) (out []byte, err error)
- func (playgo *Go) HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request)
- func (playgo *Go) HTTPHandleRun(httpresw http.ResponseWriter, httpreq *http.Request)
- func (playgo *Go) HTTPHandleTest(httpresw http.ResponseWriter, httpreq *http.Request)
- func (playgo *Go) Run(req *Request) (out []byte, err error)
- func (playgo *Go) Test(req *Request) (out []byte, err error)
- type GoOptions
- type Request
Examples ¶
Constants ¶
const GoVersion = `1.23.2`
GoVersion define the Go tool version for go.mod to be used to run the code.
const Timeout = 10 * time.Second
Timeout define the maximum time the program can be run until it get terminated.
Variables ¶
var ErrEmptyFile = errors.New(`empty File`)
ErrEmptyFile error when running Go.Test with empty play.Request.File.
Functions ¶
This section is empty.
Types ¶
type Go ¶ added in v0.60.0
type Go struct {
// contains filtered or unexported fields
}
Go define the type that can format, run, and test Go code.
func (*Go) Format ¶ added in v0.60.0
Format the Go code in the play.Request.Body and return the result to out. Any syntax error on the code will be returned as error.
Example ¶
const codeIndentMissingImport = ` package main func main() { fmt.Println("Hello, world") } ` var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: os.TempDir(), }) if err != nil { log.Fatal(err) } var req = Request{ Body: codeIndentMissingImport, } var out []byte out, err = playgo.Format(req) if err != nil { log.Fatal(err) } fmt.Printf("%s", out)
Output: package main import "fmt" func main() { fmt.Println("Hello, world") }
func (*Go) HTTPHandleFormat ¶ added in v0.60.0
func (playgo *Go) HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request)
HTTPHandleFormat define the HTTP handler for Go.Format.
Example ¶
const codeIndentMissingImport = ` package main func main() { fmt.Println("Hello, world") } ` var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: os.TempDir(), }) if err != nil { log.Fatal(err) } var req = Request{ Body: codeIndentMissingImport, } var rawbody []byte rawbody, err = json.Marshal(&req) if err != nil { log.Fatal(err) } var resprec = httptest.NewRecorder() var httpreq = httptest.NewRequest(`POST`, `/api/play/format`, bytes.NewReader(rawbody)) httpreq.Header.Set(`Content-Type`, `application/json`) var mux = http.NewServeMux() mux.HandleFunc(`POST /api/play/format`, playgo.HTTPHandleFormat) mux.ServeHTTP(resprec, httpreq) var resp = resprec.Result() rawbody, err = io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf(`%s`, rawbody)
Output: {"data":"package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Hello, world\")\n}\n","code":200}
func (*Go) HTTPHandleRun ¶ added in v0.60.0
func (playgo *Go) HTTPHandleRun( httpresw http.ResponseWriter, httpreq *http.Request, )
HTTPHandleRun define the HTTP handler for Go.Run. Each client is identified by unique cookie, so if two requests come from the same client, the previous run will be cancelled.
Example ¶
const code = ` package main import "fmt" func main() { fmt.Println("Hello, world") } ` var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: os.TempDir(), }) if err != nil { log.Fatal(err) } var req = Request{ Body: code, } var rawbody []byte rawbody, err = json.Marshal(&req) if err != nil { log.Fatal(err) } var resprec = httptest.NewRecorder() var httpreq = httptest.NewRequest(`POST`, `/api/play/run`, bytes.NewReader(rawbody)) httpreq.Header.Set(`Content-Type`, `application/json`) var mux = http.NewServeMux() mux.HandleFunc(`POST /api/play/run`, playgo.HTTPHandleRun) mux.ServeHTTP(resprec, httpreq) var resp = resprec.Result() rawbody, err = io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf(`%s`, rawbody)
Output: {"data":"Hello, world\n","code":200}
func (*Go) HTTPHandleTest ¶ added in v0.60.0
func (playgo *Go) HTTPHandleTest( httpresw http.ResponseWriter, httpreq *http.Request, )
HTTPHandleTest define the HTTP handler for testing Go.Test. Each client is identified by unique cookie, so if two requests come from the same client, the previous Test will be cancelled.
Example ¶
const code = ` package test import "testing" func TestSum(t *testing.T) { var total = sum(1, 2, 3) if total != 6 { t.Fatalf("got %d, want 6", total) } }` var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: `testdata/`, }) if err != nil { log.Fatal(err) } var req = Request{ Body: code, File: `/test_test.go`, } var rawbody []byte rawbody, err = json.Marshal(&req) if err != nil { log.Fatal(err) } var mux = http.NewServeMux() mux.HandleFunc(`POST /api/play/test`, playgo.HTTPHandleTest) var resprec = httptest.NewRecorder() var httpreq = httptest.NewRequest(`POST`, `/api/play/test`, bytes.NewReader(rawbody)) httpreq.Header.Set(`Content-Type`, `application/json`) mux.ServeHTTP(resprec, httpreq) var resp = resprec.Result() rawbody, err = io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } var rexDuration = regexp.MustCompile(`(?m)\\t(\d+\.\d+)s`) rawbody = rexDuration.ReplaceAll(rawbody, []byte(`\tXs`)) fmt.Printf(`%s`, rawbody)
Output: {"data":"ok \tgit.sr.ht/~shulhan/pakakeh.go/lib/play/testdata\tXs\n","code":200}
func (*Go) Run ¶ added in v0.60.0
Run the Go code in the play.Request.Body.
Example ¶
const codeRun = ` package main import "fmt" func main() { fmt.Println("Hello, world") }` var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: os.TempDir(), }) if err != nil { log.Fatal(err) } var req = Request{ Body: codeRun, } var out []byte out, err = playgo.Run(&req) if err != nil { log.Fatal(err) } fmt.Printf(`%s`, out)
Output: Hello, world
func (*Go) Test ¶ added in v0.60.0
Test the Go code in the play.Request.Body.
Example ¶
const codeTest = ` package test import "testing" func TestSum(t *testing.T) { var total = sum(1, 2, 3) if total != 6 { t.Fatalf("got %d, want 6", total) } }` var rexDuration = regexp.MustCompile(`(?m)\s+(\d+\.\d+)s$`) var playgo *Go var err error playgo, err = NewGo(GoOptions{ Root: `testdata/`, }) if err != nil { log.Fatal(err) } var req = Request{ Body: codeTest, File: `/test_test.go`, } var out []byte out, err = playgo.Test(&req) if err != nil { log.Fatal(err) } // Replace the test duration. out = rexDuration.ReplaceAll(out, []byte(" Xs")) fmt.Printf(`%s`, out)
Output: ok git.sr.ht/~shulhan/pakakeh.go/lib/play/testdata Xs
type GoOptions ¶ added in v0.60.0
type GoOptions struct { // Root directory of where the Go code to be written, run, or test. // Default to [os.UserCacheDir] if its not set. Root string // Version define the Go tool version in go.mod to be used to run the // code. // Default to package [GoVersion] if its not set. Version string // Timeout define the maximum time the program can be run until it // gets terminated. // Default to package [Timeout] if its not set. Timeout time.Duration // contains filtered or unexported fields }
GoOptions define the options for running and test Go code.
type Request ¶
type Request struct { // The Go version that will be used in go.mod. GoVersion string `json:"goversion"` // File define the path to test "_test.go" file. // This field is for Test. File string `json:"file"` // Body contains the Go code to be Format-ed or Run. Body string `json:"body"` // UnsafeRun define the working directory where "go run ." will be // executed directly. UnsafeRun string `json:"unsafe_run"` // WithoutRace define the field to opt out the "-race" option when // running Go code. WithoutRace bool `json:"without_race"` // contains filtered or unexported fields }