feat: initial public version
This commit is contained in:
116
engine/engine.go
Normal file
116
engine/engine.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Package engine provides code execution engines.
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nalgeon/codapi/stringx"
|
||||
)
|
||||
|
||||
// A Request initiates code execution.
|
||||
type Request struct {
|
||||
ID string `json:"id"`
|
||||
Sandbox string `json:"sandbox"`
|
||||
Command string `json:"command"`
|
||||
Files Files `json:"files"`
|
||||
}
|
||||
|
||||
// GenerateID() sets a unique ID for the request.
|
||||
func (r *Request) GenerateID() {
|
||||
r.ID = fmt.Sprintf("%s_%s_%s", r.Sandbox, r.Command, stringx.RandString(8))
|
||||
}
|
||||
|
||||
// An Execution is an output from the code execution engine.
|
||||
type Execution struct {
|
||||
ID string `json:"id"`
|
||||
OK bool `json:"ok"`
|
||||
Duration int `json:"duration"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// An ErrTimeout is returned if code execution did not complete
|
||||
// in the allowed timeframe.
|
||||
var ErrTimeout = errors.New("code execution timeout")
|
||||
|
||||
// An ErrBusy is returned when there are no engines available.
|
||||
var ErrBusy = errors.New("busy: try again later")
|
||||
|
||||
// An ExecutionError is returned if code execution failed
|
||||
// due to the application problems, not due to the problems with the code.
|
||||
type ExecutionError struct {
|
||||
msg string
|
||||
inner error
|
||||
}
|
||||
|
||||
func NewExecutionError(msg string, err error) ExecutionError {
|
||||
return ExecutionError{msg: msg, inner: err}
|
||||
}
|
||||
|
||||
func (err ExecutionError) Error() string {
|
||||
return err.msg + ": " + err.inner.Error()
|
||||
}
|
||||
|
||||
func (err ExecutionError) Unwrap() error {
|
||||
return err.inner
|
||||
}
|
||||
|
||||
// Files are a collection of files to be executed by the engine.
|
||||
type Files map[string]string
|
||||
|
||||
// First returns the contents of the first file.
|
||||
func (f Files) First() string {
|
||||
for _, content := range f {
|
||||
return content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Range iterates over the files, calling fn for each one.
|
||||
func (f Files) Range(fn func(name, content string) bool) {
|
||||
for name, content := range f {
|
||||
ok := fn(name, content)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count returns the number of files.
|
||||
func (f Files) Count() int {
|
||||
return len(f)
|
||||
}
|
||||
|
||||
// An Engine executes a specific sandbox command on the code.
|
||||
// Engines must be concurrent-safe, since they can be accessed by multiple goroutines.
|
||||
type Engine interface {
|
||||
// Exec executes the command and returns the output.
|
||||
Exec(req Request) Execution
|
||||
}
|
||||
|
||||
// Fail creates an output from an error.
|
||||
func Fail(id string, err error) Execution {
|
||||
if _, ok := err.(ExecutionError); ok {
|
||||
return Execution{
|
||||
ID: id,
|
||||
OK: false,
|
||||
Stderr: "internal error",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if errors.Is(err, ErrBusy) {
|
||||
return Execution{
|
||||
ID: id,
|
||||
OK: false,
|
||||
Stderr: err.Error(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return Execution{
|
||||
ID: id,
|
||||
OK: false,
|
||||
Stderr: err.Error(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user