feat: api result add code (#96)

This commit is contained in:
Li Wei authored and GitHub committed 2023-03-05 11:54:39 +08:00
1 parent dbbbc204fb
commit 2e639e0fef
9 files changed
+130 -89

No files matched your search

+52 -32
View File
@@ -5,7 +5,6 @@ import (
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/download"
"github.com/GopeedLab/gopeed/pkg/rest/model"
"github.com/GopeedLab/gopeed/pkg/rest/util"
"github.com/gorilla/mux"
"io"
"net/http"
@@ -15,19 +14,19 @@ import (
func Resolve(w http.ResponseWriter, r *http.Request) {
var req base.Request
if util.ReadJson(w, r, &req) {
if ReadJson(r, w, &req) {
rr, err := Downloader.Resolve(&req)
if err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
util.WriteJsonOk(w, model.NewResultWithData(rr))
WriteJson(w, model.NewOkResult(rr))
}
}
func CreateTask(w http.ResponseWriter, r *http.Request) {
var req model.CreateTask
if util.ReadJson(w, r, &req) {
if ReadJson(r, w, &req) {
var (
taskId string
err error
@@ -37,64 +36,85 @@ func CreateTask(w http.ResponseWriter, r *http.Request) {
} else if req.Req != nil {
taskId, err = Downloader.DirectCreate(req.Req, req.Opts)
} else {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg("param is required: rid or req"))
WriteJson(w, model.NewErrorResult("param invalid: rid or req", model.CodeInvalidParam))
return
}
if err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
util.WriteJsonOk(w, model.NewResultWithData(taskId))
WriteJson(w, model.NewOkResult(taskId))
}
}
func PauseTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
if err := Downloader.Pause(taskId); err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
return
}
util.WriteJsonOk(w, nil)
if err := Downloader.Pause(taskId); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func ContinueTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
if err := Downloader.Continue(taskId); err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
return
}
util.WriteJsonOk(w, nil)
if err := Downloader.Continue(taskId); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func DeleteTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
force := r.FormValue("force")
if err := Downloader.Delete(taskId, force == "true"); err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
return
}
util.WriteJsonOk(w, nil)
if err := Downloader.Delete(taskId, force == "true"); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
WriteJson(w, model.NewNilResult())
}
func GetTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
taskId := vars["id"]
task := Downloader.GetTask(taskId)
if task == nil {
util.WriteJson(w, http.StatusNotFound, model.NewResultWithMsg("task not found"))
if taskId == "" {
WriteJson(w, model.NewErrorResult("param invalid: id", model.CodeInvalidParam))
return
}
util.WriteJsonOk(w, model.NewResultWithData(task))
task := Downloader.GetTask(taskId)
if task == nil {
WriteJson(w, model.NewErrorResult("task not found", model.CodeTaskNotFound))
return
}
WriteJson(w, model.NewOkResult(task))
}
func GetTasks(w http.ResponseWriter, r *http.Request) {
status := r.FormValue("status")
if status == "" {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg("param is required: status"))
return
status = strings.Join([]string{
string(base.DownloadStatusReady),
string(base.DownloadStatusRunning),
string(base.DownloadStatusPause),
string(base.DownloadStatusError),
string(base.DownloadStatusDone),
}, ",")
}
statusArr := strings.Split(status, ",")
tasks := Downloader.GetTasks()
@@ -106,33 +126,33 @@ func GetTasks(w http.ResponseWriter, r *http.Request) {
}
}
}
util.WriteJsonOk(w, model.NewResultWithData(result))
WriteJson(w, model.NewOkResult(result))
}
func GetConfig(w http.ResponseWriter, r *http.Request) {
util.WriteJsonOk(w, model.NewResultWithData(getServerConfig()))
WriteJson(w, model.NewOkResult(getServerConfig()))
}
func PutConfig(w http.ResponseWriter, r *http.Request) {
var cfg download.DownloaderStoreConfig
if util.ReadJson(w, r, &cfg) {
if ReadJson(r, w, &cfg) {
if err := Downloader.PutConfig(&cfg); err != nil {
util.WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
}
util.WriteJsonOk(w, nil)
WriteJson(w, model.NewNilResult())
}
func DoProxy(w http.ResponseWriter, r *http.Request) {
target := r.Header.Get("X-Target-Uri")
if target == "" {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg("header is required: X-Target-Uri"))
WriteJson(w, model.NewErrorResult("param invalid: X-Target-Uri", model.CodeInvalidParam))
return
}
targetUrl, err := url.Parse(target)
if err != nil {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
r.RequestURI = ""
@@ -140,7 +160,7 @@ func DoProxy(w http.ResponseWriter, r *http.Request) {
r.Host = targetUrl.Host
resp, err := http.DefaultClient.Do(r)
if err != nil {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
defer resp.Body.Close()
@@ -159,7 +179,7 @@ func DoProxy(w http.ResponseWriter, r *http.Request) {
reader = resp.Body
}
if _, err := io.Copy(w, reader); err != nil {
util.WriteJson(w, http.StatusBadRequest, model.NewResultWithMsg(err.Error()))
WriteJson(w, model.NewErrorResult(err.Error()))
return
}
}
+34 -8
View File
@@ -1,21 +1,47 @@
package model
type RespCode int
const (
CodeOk RespCode = 0
// CodeError is the common error code
CodeError RespCode = 1000
// CodeUnauthorized is the error code for unauthorized
CodeUnauthorized RespCode = 1002
// CodeInvalidParam is the error code for invalid parameter
CodeInvalidParam RespCode = 1003
// CodeTaskNotFound is the error code for task not found
CodeTaskNotFound RespCode = 2001
)
type Result[T any] struct {
Msg string `json:"msg"`
Data T `json:"data"`
Code RespCode `json:"code"`
Msg string `json:"msg"`
Data T `json:"data"`
}
func NewResult[T any](msg string, data T) *Result[T] {
func NewOkResult[T any](data T) *Result[T] {
return &Result[T]{
Msg: msg,
Code: CodeOk,
Data: data,
}
}
func NewResultWithMsg(msg string) *Result[any] {
return NewResult[any](msg, nil)
func NewNilResult() *Result[any] {
return &Result[any]{
Code: CodeOk,
}
}
func NewResultWithData[T any](data T) *Result[T] {
return NewResult[T]("", data)
func NewErrorResult(msg string, code ...RespCode) *Result[any] {
// if code is not provided, the default code is CodeError
c := CodeError
if len(code) > 0 {
c = code[0]
}
return &Result[any]{
Code: c,
Msg: msg,
}
}
+16 -2
View File
@@ -2,9 +2,9 @@ package rest
import (
"context"
"encoding/json"
"github.com/GopeedLab/gopeed/pkg/download"
"github.com/GopeedLab/gopeed/pkg/rest/model"
restUtil "github.com/GopeedLab/gopeed/pkg/rest/util"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
@@ -98,7 +98,7 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
r.Use(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Api-Token") != startCfg.ApiToken {
restUtil.WriteJson(w, http.StatusUnauthorized, model.NewResultWithMsg("invalid token"))
WriteJson(w, model.NewErrorResult("invalid token", model.CodeUnauthorized))
return
}
h.ServeHTTP(w, r)
@@ -113,3 +113,17 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
)(r)}
return srv, listener, nil
}
func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
WriteJson(w, model.NewErrorResult(err.Error()))
return false
}
return true
}
func WriteJson(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(v)
}
+13 -11
View File
@@ -150,7 +150,7 @@ func TestDeleteTask(t *testing.T) {
time.Sleep(time.Millisecond * 200)
httpRequestCheckOk[any](http.MethodDelete, "/api/v1/tasks/"+taskId, nil)
code, _ := httpRequest[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
checkCode(code, http.StatusNotFound)
checkCode(code, model.CodeTaskNotFound)
})
}
@@ -160,7 +160,7 @@ func TestDeleteTaskForce(t *testing.T) {
time.Sleep(time.Millisecond * 200)
httpRequestCheckOk[any](http.MethodDelete, "/api/v1/tasks/"+taskId+"?force=true", nil)
code, _ := httpRequest[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
checkCode(code, http.StatusNotFound)
checkCode(code, model.CodeTaskNotFound)
if _, err := os.Stat(test.DownloadFile); !errors.Is(err, os.ErrNotExist) {
t.Errorf("DeleteTaskForce() got = %v, want %v", err, os.ErrNotExist)
}
@@ -179,8 +179,7 @@ func TestGetTasks(t *testing.T) {
httpRequestCheckOk[string](http.MethodPost, fmt.Sprintf("/api/v1/tasks?status=%s,%s",
base.DownloadStatusReady, base.DownloadStatusRunning), createReq)
code, _ := httpRequest[[]*download.Task](http.MethodGet, "/api/v1/tasks", nil)
checkCode(code, http.StatusBadRequest)
httpRequestCheckOk[[]*download.Task](http.MethodGet, "/api/v1/tasks", nil)
wg.Wait()
r := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s",
@@ -245,12 +244,12 @@ func TestAuthorization(t *testing.T) {
}()
code, _ := doHttpRequest[any](http.MethodGet, "/api/v1/config", nil, nil)
checkCode(code, http.StatusUnauthorized)
checkCode(code, model.CodeUnauthorized)
code, _ = doHttpRequest[any](http.MethodGet, "/api/v1/config", map[string]string{
"X-Api-Token": cfg.ApiToken,
}, nil)
checkCode(code, http.StatusOK)
checkCode(code, model.CodeOk)
}
@@ -308,17 +307,20 @@ func doHttpRequest0(method string, path string, headers map[string]string, body
if err != nil {
panic(err)
}
if response.StatusCode != http.StatusOK {
panic(fmt.Sprintf("http request failed, status code: %d", response.StatusCode))
}
return response.StatusCode, respBody
}
func doHttpRequest[T any](method string, path string, headers map[string]string, body any) (int, *model.Result[T]) {
code, respBody := doHttpRequest0(method, path, headers, body)
_, respBody := doHttpRequest0(method, path, headers, body)
var r model.Result[T]
if err := json.Unmarshal(respBody, &r); err != nil {
panic(err)
}
return code, &r
return int(r.Code), &r
}
func httpRequest[T any](method string, path string, body any) (int, *model.Result[T]) {
@@ -332,11 +334,11 @@ func httpRequestCheckOk[T any](method string, path string, body any) T {
}
func checkOk(code int) {
checkCode(code, 200)
checkCode(code, model.CodeOk)
}
func checkCode(code int, exceptCode int) {
if code != exceptCode {
func checkCode(code int, exceptCode model.RespCode) {
if code != int(exceptCode) {
panic(fmt.Sprintf("code got = %d, want %d", code, exceptCode))
}
}
-29
View File
@@ -1,29 +0,0 @@
package util
import (
"encoding/json"
"github.com/GopeedLab/gopeed/pkg/rest/model"
"net/http"
)
func ReadJson(w http.ResponseWriter, r *http.Request, v any) bool {
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
WriteJson(w, http.StatusInternalServerError, model.NewResultWithMsg(err.Error()))
return false
}
return true
}
func WriteJsonOk(w http.ResponseWriter, v any) {
if v == nil {
WriteJson(w, http.StatusOK, model.NewResultWithData[any](nil))
} else {
WriteJson(w, http.StatusOK, v)
}
}
func WriteJson(w http.ResponseWriter, code int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
json.NewEncoder(w).Encode(v)
}
+7 -5
View File
@@ -70,15 +70,17 @@ Future<T> _parse<T>(
try {
var resp = await fetch();
if (fromJsonT != null) {
return Result<T>.fromJson(resp.data, fromJsonT).data as T;
final result = Result<T>.fromJson(resp.data, fromJsonT);
if (result.code == 0) {
return result.data as T;
} else {
throw Exception(result);
}
} else {
return null as T;
}
} on DioError catch (e) {
if (e.response == null) {
throw Exception("Server error");
}
throw Exception(Result.fromJson(e.response?.data, (_) => null).msg);
throw Exception(Result(code: 1000, msg: e.message));
}
}
+3
View File
@@ -4,10 +4,12 @@ part 'result.g.dart';
@JsonSerializable(genericArgumentFactories: true)
class Result<T> {
int code;
String? msg;
T? data;
Result({
required this.code,
this.msg,
this.data,
});
@@ -18,6 +20,7 @@ class Result<T> {
) =>
_$ResultFromJson(json, fromJsonT);
Map<String, dynamic> toJson() => {
'code': code,
'msg': msg,
'data': data is List
? (data as dynamic)?.map((e) => e.toJson()).toList()
+4 -1
View File
@@ -11,6 +11,7 @@ Result<T> _$ResultFromJson<T>(
T Function(Object? json) fromJsonT,
) =>
Result<T>(
code: json['code'] as int,
msg: json['msg'] as String?,
data: _$nullableGenericFromJson(json['data'], fromJsonT),
);
@@ -19,7 +20,9 @@ Map<String, dynamic> _$ResultToJson<T>(
Result<T> instance,
Object? Function(T value) toJsonT,
) {
final val = <String, dynamic>{};
final val = <String, dynamic>{
'code': instance.code,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
+1 -1
View File
@@ -1004,5 +1004,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.18.0 <4.0.0"
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"