From fb6b31485607a0c2f70b77e5da9d4a13e17a0970 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Thu, 12 Dec 2024 00:27:09 +0700 Subject: lib/play: move all HTTP related methods to its own file --- lib/play/http.go | 159 ++++++++++++++++++++++++++++++++++++++++- lib/play/http_example_test.go | 83 ++++++++++++++++++++++ lib/play/http_test.go | 133 +++++++++++++++++++++++++++++++++- lib/play/play.go | 161 ------------------------------------------ lib/play/play_example_test.go | 86 ---------------------- lib/play/play_test.go | 139 ------------------------------------ 6 files changed, 372 insertions(+), 389 deletions(-) diff --git a/lib/play/http.go b/lib/play/http.go index 34cc618d..3910a74d 100644 --- a/lib/play/http.go +++ b/lib/play/http.go @@ -6,6 +6,7 @@ package play import ( "encoding/json" + "io" "log" "net/http" @@ -13,6 +14,161 @@ import ( libhttp "git.sr.ht/~shulhan/pakakeh.go/lib/http" ) +// HTTPHandleFormat define the HTTP handler for formating Go code. +func HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request) { + var ( + logp = `HTTPHandleFormat` + resp = libhttp.EndpointResponse{} + + req Request + rawbody []byte + err error + ) + + var contentType = httpreq.Header.Get(libhttp.HeaderContentType) + if contentType != libhttp.ContentTypeJSON { + resp.Code = http.StatusUnsupportedMediaType + resp.Name = `ERR_CONTENT_TYPE` + goto out + } + + rawbody, err = io.ReadAll(httpreq.Body) + if err != nil { + resp.Code = http.StatusInternalServerError + resp.Name = `ERR_INTERNAL` + resp.Message = err.Error() + goto out + } + + err = json.Unmarshal(rawbody, &req) + if err != nil { + resp.Code = http.StatusBadRequest + resp.Name = `ERR_BAD_REQUEST` + resp.Message = err.Error() + goto out + } + + rawbody, err = Format(req) + if err != nil { + resp.Code = http.StatusUnprocessableEntity + resp.Name = `ERR_CODE` + resp.Message = err.Error() + goto out + } + + resp.Code = http.StatusOK + resp.Data = string(rawbody) +out: + rawbody, err = json.Marshal(resp) + if err != nil { + log.Printf(`%s: %s`, logp, err) + resp.Code = http.StatusInternalServerError + } + httpresw.Header().Set(libhttp.HeaderContentType, + libhttp.ContentTypeJSON) + httpresw.WriteHeader(resp.Code) + httpresw.Write(rawbody) +} + +// 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. +func HTTPHandleRun(httpresw http.ResponseWriter, httpreq *http.Request) { + var ( + logp = `HTTPHandleRun` + + req *Request + resp *libhttp.EndpointResponse + rawb []byte + err error + ) + + req, resp = readRequest(httpreq) + if resp != nil { + goto out + } + + rawb, err = Run(req) + if err != nil { + resp = &libhttp.EndpointResponse{ + E: liberrors.E{ + Message: err.Error(), + Name: `ERR_INTERNAL`, + Code: http.StatusInternalServerError, + }, + } + goto out + } + + http.SetCookie(httpresw, req.cookieSid) + resp = &libhttp.EndpointResponse{} + resp.Code = http.StatusOK + resp.Data = string(rawb) +out: + rawb, err = json.Marshal(resp) + if err != nil { + log.Printf(`%s: %s`, logp, err) + resp.Code = http.StatusInternalServerError + } + httpresw.Header().Set(libhttp.HeaderContentType, + libhttp.ContentTypeJSON) + httpresw.WriteHeader(resp.Code) + httpresw.Write(rawb) +} + +func readRequest(httpreq *http.Request) ( + req *Request, + resp *libhttp.EndpointResponse, +) { + var contentType = httpreq.Header.Get(libhttp.HeaderContentType) + if contentType != libhttp.ContentTypeJSON { + resp = &libhttp.EndpointResponse{ + E: liberrors.E{ + Message: `invalid content type`, + Name: `ERR_CONTENT_TYPE`, + Code: http.StatusUnsupportedMediaType, + }, + } + return nil, resp + } + + var ( + rawbody []byte + err error + ) + + rawbody, err = io.ReadAll(httpreq.Body) + if err != nil { + resp = &libhttp.EndpointResponse{ + E: liberrors.E{ + Message: err.Error(), + Name: `ERR_INTERNAL`, + Code: http.StatusInternalServerError, + }, + } + return nil, resp + } + + err = json.Unmarshal(rawbody, &req) + if err != nil { + resp = &libhttp.EndpointResponse{ + E: liberrors.E{ + Message: err.Error(), + Name: `ERR_BAD_REQUEST`, + Code: http.StatusBadRequest, + }, + } + return nil, resp + } + + req.cookieSid, err = httpreq.Cookie(cookieNameSid) + if err != nil { + // Ignore the error if cookie is not exist, we wiil generate + // one later. + } + return req, nil +} + // 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. @@ -53,7 +209,8 @@ out: log.Printf(`%s: %s`, logp, err) resp.Code = http.StatusInternalServerError } - httpresw.Header().Set(libhttp.HeaderContentType, libhttp.ContentTypeJSON) + httpresw.Header().Set(libhttp.HeaderContentType, + libhttp.ContentTypeJSON) httpresw.WriteHeader(resp.Code) httpresw.Write(rawb) } diff --git a/lib/play/http_example_test.go b/lib/play/http_example_test.go index 6752e175..a25bf8c7 100644 --- a/lib/play/http_example_test.go +++ b/lib/play/http_example_test.go @@ -15,6 +15,89 @@ import ( "regexp" ) +func ExampleHTTPHandleFormat() { + 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 ExampleHTTPHandleRun() { + 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 ExampleHTTPHandleTest() { const code = ` package test diff --git a/lib/play/http_test.go b/lib/play/http_test.go index 7f79bcab..f3bdc3d6 100644 --- a/lib/play/http_test.go +++ b/lib/play/http_test.go @@ -16,6 +16,133 @@ import ( "git.sr.ht/~shulhan/pakakeh.go/lib/test" ) +func TestHTTPHandleFormat(t *testing.T) { + type testCase struct { + tag string + contentType string + } + + var ( + tdata *test.Data + err error + ) + tdata, err = test.LoadData(`testdata/httpHandleFormat_test.txt`) + if err != nil { + t.Fatal(err) + } + + var listCase = []testCase{{ + tag: `invalid_content_type`, + }, { + tag: `no_package`, + contentType: libhttp.ContentTypeJSON, + }, { + tag: `indent_and_missing_import`, + contentType: libhttp.ContentTypeJSON, + }} + + var ( + req Request + tcase testCase + rawb []byte + ) + for _, tcase = range listCase { + req.Body = string(tdata.Input[tcase.tag]) + + rawb, err = json.Marshal(&req) + if err != nil { + t.Fatal(err) + } + + var req = httptest.NewRequest(`POST`, `/`, + bytes.NewReader(rawb)) + req.Header.Set(libhttp.HeaderContentType, tcase.contentType) + + var httpWriter = httptest.NewRecorder() + + HTTPHandleFormat(httpWriter, req) + + var result = httpWriter.Result() + rawb, err = httputil.DumpResponse(result, true) + if err != nil { + t.Fatal(err) + } + rawb = bytes.ReplaceAll(rawb, []byte("\r"), []byte("")) + + var exp = string(tdata.Output[tcase.tag]) + test.Assert(t, tcase.tag, exp, string(rawb)) + } +} + +func TestHTTPHandleRun(t *testing.T) { + type testCase struct { + tag string + contentType string + req Request + } + + var ( + tdata *test.Data + err error + ) + tdata, err = test.LoadData(`testdata/httpHandleRun_test.txt`) + if err != nil { + t.Fatal(err) + } + + var listCase = []testCase{{ + tag: `no-content-type`, + }, { + tag: `helloworld`, + contentType: libhttp.ContentTypeJSON, + }, { + tag: `nopackage`, + contentType: libhttp.ContentTypeJSON, + }, { + tag: `nopackage`, + contentType: libhttp.ContentTypeJSON, + }, { + tag: `go121_for`, + contentType: libhttp.ContentTypeJSON, + req: Request{ + GoVersion: `1.21.13`, + WithoutRace: true, + }, + }} + + var ( + tcase testCase + rawb []byte + ) + for _, tcase = range listCase { + tcase.req.Body = string(tdata.Input[tcase.tag]) + + rawb, err = json.Marshal(&tcase.req) + if err != nil { + t.Fatal(err) + } + + var httpReq = httptest.NewRequest(`POST`, `/`, + bytes.NewReader(rawb)) + httpReq.Header.Set(libhttp.HeaderContentType, + tcase.contentType) + + var httpWriter = httptest.NewRecorder() + + HTTPHandleRun(httpWriter, httpReq) + + var result = httpWriter.Result() + rawb, err = httputil.DumpResponse(result, true) + if err != nil { + t.Fatal(err) + } + rawb = bytes.ReplaceAll(rawb, []byte("\r"), []byte("")) + + var exp = string(tdata.Output[tcase.tag]) + test.Assert(t, tcase.tag, exp, string(rawb)) + } +} + func TestHTTPHandleTest(t *testing.T) { type testCase struct { tag string @@ -61,8 +188,10 @@ func TestHTTPHandleTest(t *testing.T) { t.Fatal(err) } - var httpReq = httptest.NewRequest(`POST`, `/`, bytes.NewReader(rawb)) - httpReq.Header.Set(libhttp.HeaderContentType, tcase.contentType) + var httpReq = httptest.NewRequest(`POST`, `/`, + bytes.NewReader(rawb)) + httpReq.Header.Set(libhttp.HeaderContentType, + tcase.contentType) var httpWriter = httptest.NewRecorder() diff --git a/lib/play/play.go b/lib/play/play.go index f89101a5..9c6ff8a2 100644 --- a/lib/play/play.go +++ b/lib/play/play.go @@ -84,20 +84,13 @@ package play import ( - "encoding/json" "errors" "fmt" - "io" - "log" - "net/http" "os" "path/filepath" "time" "golang.org/x/tools/imports" - - liberrors "git.sr.ht/~shulhan/pakakeh.go/lib/errors" - libhttp "git.sr.ht/~shulhan/pakakeh.go/lib/http" ) // ErrEmptyFile error when running [Test] with empty File field in the @@ -152,160 +145,6 @@ func Format(req Request) (out []byte, err error) { return fmtbody, nil } -// HTTPHandleFormat define the HTTP handler for formating Go code. -func HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request) { - var ( - logp = `HTTPHandleFormat` - resp = libhttp.EndpointResponse{} - - req Request - rawbody []byte - err error - ) - - httpresw.Header().Set(libhttp.HeaderContentType, libhttp.ContentTypeJSON) - - var contentType = httpreq.Header.Get(libhttp.HeaderContentType) - if contentType != libhttp.ContentTypeJSON { - resp.Code = http.StatusUnsupportedMediaType - resp.Name = `ERR_CONTENT_TYPE` - goto out - } - - rawbody, err = io.ReadAll(httpreq.Body) - if err != nil { - resp.Code = http.StatusInternalServerError - resp.Name = `ERR_INTERNAL` - resp.Message = err.Error() - goto out - } - - err = json.Unmarshal(rawbody, &req) - if err != nil { - resp.Code = http.StatusBadRequest - resp.Name = `ERR_BAD_REQUEST` - resp.Message = err.Error() - goto out - } - - rawbody, err = Format(req) - if err != nil { - resp.Code = http.StatusUnprocessableEntity - resp.Name = `ERR_CODE` - resp.Message = err.Error() - goto out - } - - resp.Code = http.StatusOK - resp.Data = string(rawbody) -out: - rawbody, err = json.Marshal(resp) - if err != nil { - log.Printf(`%s: %s`, logp, err) - resp.Code = http.StatusInternalServerError - } - - httpresw.WriteHeader(resp.Code) - httpresw.Write(rawbody) -} - -// 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. -func HTTPHandleRun(httpresw http.ResponseWriter, httpreq *http.Request) { - var ( - logp = `HTTPHandleRun` - - req *Request - resp *libhttp.EndpointResponse - rawb []byte - err error - ) - - httpresw.Header().Set(libhttp.HeaderContentType, libhttp.ContentTypeJSON) - - req, resp = readRequest(httpreq) - if resp != nil { - goto out - } - - rawb, err = Run(req) - if err != nil { - resp = &libhttp.EndpointResponse{ - E: liberrors.E{ - Message: err.Error(), - Name: `ERR_INTERNAL`, - Code: http.StatusInternalServerError, - }, - } - goto out - } - - http.SetCookie(httpresw, req.cookieSid) - resp = &libhttp.EndpointResponse{} - resp.Code = http.StatusOK - resp.Data = string(rawb) -out: - rawb, err = json.Marshal(resp) - if err != nil { - log.Printf(`%s: %s`, logp, err) - resp.Code = http.StatusInternalServerError - } - httpresw.WriteHeader(resp.Code) - httpresw.Write(rawb) -} - -func readRequest(httpreq *http.Request) (req *Request, resp *libhttp.EndpointResponse) { - var contentType = httpreq.Header.Get(libhttp.HeaderContentType) - if contentType != libhttp.ContentTypeJSON { - resp = &libhttp.EndpointResponse{ - E: liberrors.E{ - Message: `invalid content type`, - Name: `ERR_CONTENT_TYPE`, - Code: http.StatusUnsupportedMediaType, - }, - } - return nil, resp - } - - var ( - rawbody []byte - err error - ) - - rawbody, err = io.ReadAll(httpreq.Body) - if err != nil { - resp = &libhttp.EndpointResponse{ - E: liberrors.E{ - Message: err.Error(), - Name: `ERR_INTERNAL`, - Code: http.StatusInternalServerError, - }, - } - return nil, resp - } - - err = json.Unmarshal(rawbody, &req) - if err != nil { - resp = &libhttp.EndpointResponse{ - E: liberrors.E{ - Message: err.Error(), - Name: `ERR_BAD_REQUEST`, - Code: http.StatusBadRequest, - }, - } - return nil, resp - } - - req.cookieSid, err = httpreq.Cookie(cookieNameSid) - if err != nil { - // Ignore the error if cookie is not exist, we wiil generate - // one later. - } - - return req, nil -} - // Run the Go code in the [Request.Body]. func Run(req *Request) (out []byte, err error) { var logp = `Run` diff --git a/lib/play/play_example_test.go b/lib/play/play_example_test.go index c89d46e8..46310e05 100644 --- a/lib/play/play_example_test.go +++ b/lib/play/play_example_test.go @@ -5,13 +5,8 @@ package play import ( - "bytes" - "encoding/json" "fmt" - "io" "log" - "net/http" - "net/http/httptest" "regexp" ) @@ -45,87 +40,6 @@ func main() { //} } -func ExampleHTTPHandleFormat() { - var mux = http.NewServeMux() - mux.HandleFunc(`POST /api/play/format`, HTTPHandleFormat) - - 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`) - - 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 ExampleHTTPHandleRun() { - var mux = http.NewServeMux() - mux.HandleFunc(`POST /api/play/run`, HTTPHandleRun) - - const codeRun = ` -package main -import "fmt" -func main() { - fmt.Println("Hello, world") -} -` - var req = Request{ - Body: codeRun, - } - 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`) - - 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 ExampleRun() { const codeRun = ` package main diff --git a/lib/play/play_test.go b/lib/play/play_test.go index 48075347..db7a29af 100644 --- a/lib/play/play_test.go +++ b/lib/play/play_test.go @@ -5,11 +5,7 @@ package play import ( - "bytes" - "encoding/json" "net/http" - "net/http/httptest" - "net/http/httputil" "os" "regexp" "strings" @@ -18,7 +14,6 @@ import ( "testing" "time" - libhttp "git.sr.ht/~shulhan/pakakeh.go/lib/http" "git.sr.ht/~shulhan/pakakeh.go/lib/test" ) @@ -63,140 +58,6 @@ func TestFormat(t *testing.T) { } } -func TestHTTPHandleFormat(t *testing.T) { - type testCase struct { - tag string - contentType string - } - - var ( - tdata *test.Data - err error - ) - tdata, err = test.LoadData(`testdata/httpHandleFormat_test.txt`) - if err != nil { - t.Fatal(err) - } - - var listCase = []testCase{{ - tag: `invalid_content_type`, - }, { - tag: `no_package`, - contentType: libhttp.ContentTypeJSON, - }, { - tag: `indent_and_missing_import`, - contentType: libhttp.ContentTypeJSON, - }} - - var ( - withBody = true - - req Request - tcase testCase - rawb []byte - body bytes.Buffer - ) - for _, tcase = range listCase { - req.Body = string(tdata.Input[tcase.tag]) - - rawb, err = json.Marshal(&req) - if err != nil { - t.Fatal(err) - } - body.Reset() - body.Write(rawb) - - var req *http.Request = httptest.NewRequest(`POST`, `/`, &body) - req.Header.Set(libhttp.HeaderContentType, tcase.contentType) - - var writer *httptest.ResponseRecorder = httptest.NewRecorder() - - HTTPHandleFormat(writer, req) - - var result *http.Response = writer.Result() - rawb, err = httputil.DumpResponse(result, withBody) - if err != nil { - t.Fatal(err) - } - rawb = bytes.ReplaceAll(rawb, []byte("\r"), []byte("")) - - var exp = string(tdata.Output[tcase.tag]) - test.Assert(t, tcase.tag, exp, string(rawb)) - } -} - -func TestHTTPHandleRun(t *testing.T) { - type testCase struct { - tag string - contentType string - req Request - } - - var ( - tdata *test.Data - err error - ) - tdata, err = test.LoadData(`testdata/httpHandleRun_test.txt`) - if err != nil { - t.Fatal(err) - } - - var listCase = []testCase{{ - tag: `no-content-type`, - }, { - tag: `helloworld`, - contentType: libhttp.ContentTypeJSON, - }, { - tag: `nopackage`, - contentType: libhttp.ContentTypeJSON, - }, { - tag: `nopackage`, - contentType: libhttp.ContentTypeJSON, - }, { - tag: `go121_for`, - contentType: libhttp.ContentTypeJSON, - req: Request{ - GoVersion: `1.21.13`, - WithoutRace: true, - }, - }} - - var ( - withBody = true - - tcase testCase - rawb []byte - body bytes.Buffer - ) - for _, tcase = range listCase { - tcase.req.Body = string(tdata.Input[tcase.tag]) - - rawb, err = json.Marshal(&tcase.req) - if err != nil { - t.Fatal(err) - } - body.Reset() - body.Write(rawb) - - var httpReq *http.Request = httptest.NewRequest(`POST`, `/`, &body) - httpReq.Header.Set(libhttp.HeaderContentType, tcase.contentType) - - var writer *httptest.ResponseRecorder = httptest.NewRecorder() - - HTTPHandleRun(writer, httpReq) - - var result *http.Response = writer.Result() - rawb, err = httputil.DumpResponse(result, withBody) - if err != nil { - t.Fatal(err) - } - rawb = bytes.ReplaceAll(rawb, []byte("\r"), []byte("")) - - var exp = string(tdata.Output[tcase.tag]) - test.Assert(t, tcase.tag, exp, string(rawb)) - } -} - func TestRun(t *testing.T) { var ( tdata *test.Data -- cgit v1.3