play

package
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 1, 2025 License: BSD-3-Clause Imports: 20 Imported by: 0

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

Examples

Constants

View Source
const GoVersion = `1.23.2`

GoVersion define the Go tool version for go.mod to be used to run the code.

View Source
const Timeout = 10 * time.Second

Timeout define the maximum time the program can be run until it get terminated.

Variables

View Source
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 NewGo added in v0.60.0

func NewGo(opts GoOptions) (playgo *Go, err error)

NewGo create and initialize new Go tools.

func (*Go) Format added in v0.60.0

func (playgo *Go) Format(req Request) (out []byte, err error)

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

func (playgo *Go) Run(req *Request) (out []byte, err error)

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

func (playgo *Go) Test(req *Request) (out []byte, err error)

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
}

Request for calling Go.Format, Go.Run, or Go.Test.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL