play

package
v0.59.0 Latest Latest
Warning

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

Go to latest
Published: Jan 5, 2025 License: BSD-3-Clause Imports: 19 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

HTTP APIs for formatting and running Go code accept JSON content type, with the following 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 as 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 return the following JSON response format,

{
	"code": <integer, HTTP status code>,
	"name": <string, error type>,
	"message": <string, optional message>,
	"data": <string>
}

For the HTTPHandleFormat, the response "data" contains the formatted Go code. For the HTTPHandleRun, the response "data" contains the output from running the Go code, the "message" contains an error pre-Run, like bad request or file system related error.

Unsafe run

As exceptional, the Run and 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 HTTP server working directory. Once request accepted it will change the directory into "unsafe_run" first and then run "go run ." directly. Go code that executed inside "unsafe_run" should be not modifiable and safe from mallicious execution.

Testing

For testing, since the test must run inside the directory that contains the Go file to be tested, the 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 "file". The test will run, by default, with "go test -count=1 -race $dirname" where "$dirname" is the path directory to the "file" relative to where the program is running. If "without_race" is true, the test command will not run with "-race" option.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrEmptyFile = errors.New(`empty File`)

ErrEmptyFile error when running Test with empty File field in the Request.

View Source
var GoVersion = `1.23.2`

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

View Source
var Timeout = 10 * time.Second

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

Functions

func Format

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

Format the Go code in the [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 req = Request{
	Body: codeIndentMissingImport,
}
var (
	out []byte
	err error
)
out, err = Format(req)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("%s", out)
Output:

package main

import "fmt"

func main() {
	fmt.Println("Hello, world")
}

func HTTPHandleFormat

func HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request)

HTTPHandleFormat define the HTTP handler for formating Go code.

Example
const codeIndentMissingImport = `
package main
func main() {
  fmt.Println("Hello, world")
}
`
var req = Request{
	Body: codeIndentMissingImport,
}
var (
	rawbody []byte
	err     error
)
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`, 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 HTTPHandleRun

func HTTPHandleRun(httpresw http.ResponseWriter, httpreq *http.Request)

HTTPHandleRun define the HTTP handler for running Go code. Each client is identified by unique cookie, so if two Run 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 req = Request{
	Body: code,
}
var (
	rawbody []byte
	err     error
)
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`, 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 HTTPHandleTest added in v0.59.0

func HTTPHandleTest(httpresw http.ResponseWriter, httpreq *http.Request)

HTTPHandleTest define the HTTP handler for testing Go code. Each client is identified by unique cookie, so if two Run 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 req = Request{
	Body: code,
	File: `testdata/test_test.go`,
}
var (
	rawbody []byte
	err     error
)
rawbody, err = json.Marshal(&req)
if err != nil {
	log.Fatal(err)
}

var mux = http.NewServeMux()

mux.HandleFunc(`POST /api/play/test`, 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 Run

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

Run the Go code in the [Request.Body].

Example
const codeRun = `
package main
import "fmt"
func main() {
	fmt.Println("Hello, world")
}`

var req = Request{
	Body: codeRun,
}
var (
	out []byte
	err error
)
out, err = Run(&req)
if err != nil {
	fmt.Printf(`error: %s`, err)
}
fmt.Printf(`%s`, out)
Output:

Hello, world

func Test added in v0.59.0

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

Test the Go code in the [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 req = Request{
	Body: codeTest,
	File: `testdata/test_test.go`,
}
var (
	rexDuration = regexp.MustCompile(`(?m)\s+(\d+\.\d+)s$`)
	out         []byte
	err         error
)
out, err = Test(&req)
if err != nil {
	fmt.Printf(`error: %s`, 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

Types

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 option opt out "-race" when running Go code.
	WithoutRace bool `json:"without_race"`
	// contains filtered or unexported fields
}

Request for calling Format and Run.

Jump to

Keyboard shortcuts

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