0x4a52466c696e74 6 månader sedan
förälder
incheckning
388c9e442c
12 ändrade filer med 314 tillägg och 309 borttagningar
  1. 11 11
      command.go
  2. 4 38
      error.go
  3. 4 3
      example/application.go
  4. 25 22
      example/user.go
  5. 1 1
      fielder.go
  6. 11 65
      request.go
  7. 6 146
      response.go
  8. 1 0
      rest_http/error.go
  9. 87 0
      rest_http/request.go
  10. 126 0
      rest_http/response.go
  11. 12 17
      rest_http/rest.go
  12. 26 6
      serialize.go

+ 11 - 11
command.go

@@ -3,17 +3,17 @@ package rest
 type IApplication interface {
 	Addr() string
 	Secret() []byte
-	Executer(r *Request) (IExecuter, bool)
+	Executer(r IRequest) (IExecuter, bool)
 }
 
 ///////////////////////////////////////////////
 
 type IValidator interface {
-	Validate(r *Request) *Response
+	Validate(r IRequest) IResponse
 }
 
 type IExecuter interface {
-	Execute(r *Request) *Response
+	Execute(r IRequest) IResponse
 }
 
 type ICommand interface {
@@ -35,42 +35,42 @@ type Command struct {
 	executer  IExecuter
 }
 
-func (s *Command) Validate(r *Request) *Response {
+func (s *Command) Validate(r IRequest) IResponse {
 	return s.validator.Validate(r)
 }
 
-func (s *Command) Execute(r *Request) *Response {
+func (s *Command) Execute(r IRequest) IResponse {
 	return s.executer.Execute(r)
 }
 
 ///////////////////////////////////////////////
 
-func NewValidator(method func(r *Request) *Response) *Validator {
+func NewValidator(method func(r IRequest) IResponse) *Validator {
 	return &Validator{
 		method,
 	}
 }
 
 type Validator struct {
-	method func(r *Request) *Response
+	method func(r IRequest) IResponse
 }
 
-func (s *Validator) Validate(r *Request) *Response {
+func (s *Validator) Validate(r IRequest) IResponse {
 	return s.method(r)
 }
 
 ///////////////////////////////////////////////
 
-func NewExecuter(method func(r *Request) *Response) *Executer {
+func NewExecuter(method func(r IRequest) IResponse) *Executer {
 	return &Executer{
 		method,
 	}
 }
 
 type Executer struct {
-	method func(r *Request) *Response
+	method func(r IRequest) IResponse
 }
 
-func (s *Executer) Execute(r *Request) *Response {
+func (s *Executer) Execute(r IRequest) IResponse {
 	return s.method(r)
 }

+ 4 - 38
errors.go → error.go

@@ -11,16 +11,7 @@ type IErrorArgs interface {
 	Map() json.Map
 }
 
-/////////////////////////////////////////////////////
-
-func NewErrPrtexpected(field string) IErrorArgs {
-	return &Error{
-		name: "PtrExpected",
-		args: json.Map{
-			"field": field,
-		},
-	}
-}
+//////////////////////////////////
 
 func ErrorFiled(field, message string) IErrorArgs {
 	return &Error{
@@ -32,33 +23,6 @@ func ErrorFiled(field, message string) IErrorArgs {
 	}
 }
 
-func NewErrFieldRequired(field string) IErrorArgs {
-	return &Error{
-		name: "FieldRequired",
-		args: json.Map{
-			"field": field,
-		},
-	}
-}
-
-func NewErrFieldType(field, expectedType, receivedType, message string, index int) IErrorArgs {
-	args := json.Map{
-		"field":    field,
-		"expected": expectedType,
-		"received": receivedType,
-	}
-	if len(message) > 0 {
-		args["message"] = message
-	}
-	if index >= 0 {
-		args["index"] = index
-	}
-	return &Error{
-		name: "FieldType",
-		args: args,
-	}
-}
-
 func NewError(name string, args json.Map) IErrorArgs {
 	return &Error{
 		name: name,
@@ -66,7 +30,7 @@ func NewError(name string, args json.Map) IErrorArgs {
 	}
 }
 
-func NewErrorMessage(name, text string) IErrorArgs {
+func ErrorMessage(name, text string) IErrorArgs {
 	return &Error{
 		name: name,
 		args: json.Map{
@@ -75,6 +39,8 @@ func NewErrorMessage(name, text string) IErrorArgs {
 	}
 }
 
+//////////////////////////////////////////////
+
 type Error struct {
 	name string
 	args json.Map

+ 4 - 3
example/application.go

@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"git.ali33.ru/fcg-xvii/rest"
+	"git.ali33.ru/fcg-xvii/rest/rest_http"
 )
 
 // ExapmleApp реализует интерфейс IApplication, который отдает серверу параметры подключения,
@@ -33,8 +34,8 @@ func (s *ExampleApp) Secret() []byte {
 	return s.secret
 }
 
-func (s *ExampleApp) Executer(r *rest.Request) (rest.IExecuter, bool) {
-	switch r.RPath() {
+func (s *ExampleApp) Executer(r rest.IRequest) (rest.IExecuter, bool) {
+	switch r.Command() {
 	case "/user/register":
 		return &ExampleRequestRegister{}, true
 	case "/user/info":
@@ -68,7 +69,7 @@ func ExampleNew() {
 		},
 	)
 	// создаем сервер
-	restServ := rest.New(App)
+	restServ := rest_http.New(App)
 	// пробуем запустить его. Если через секунду запуск не удался, будет возвращена ошибка
 	if err := restServ.Listen(time.Second); err != nil {
 		// ошибка запуска

+ 25 - 22
example/user.go

@@ -44,23 +44,26 @@ type ExampleRequestRegister struct {
 	group *ExampleGroup
 }
 
-func (s *ExampleRequestRegister) Validate(req *rest.Request) *rest.Response {
+func (s *ExampleRequestRegister) Validate(req rest.IRequest) rest.IResponse {
 	// пользователь не должен быть авторизован
 	if req.Auth() != nil {
-		return rest.ResponseErrorMessage("AlreadyAuthorized", "User is already authorized", 500)
+		return req.ResponseError(
+			500,
+			rest.ErrorMessage("AlreadyAuthorized", "User is already authorized"),
+		)
 	}
 	// проверяем имя
 	if s.Name = strings.TrimSpace(s.Name); len(s.Name) == 0 {
-		return rest.ResponseErrField("name", "expected not empty string")
+		return req.ResponseError(500, rest.ErrorFiled("name", "expected not empty string"))
 	}
 	// проверяем пароль
 	if len(s.Password) < 3 {
-		return rest.ResponseErrField("password", "expected minimum 3 symbols")
+		return req.ResponseError(500, rest.ErrorFiled("password", "expected minimum 3 symbols"))
 	}
 	// вилидируем группу
 	group, check := App.groups.Load(s.GroupID)
 	if !check {
-		return rest.ResponseErrorMessage("GroupNotFound", "Group is not found", 500)
+		return req.ResponseError(500, rest.ErrorMessage("GroupNotFound", "Group is not found"))
 	}
 	// Устанавливаем группу для использования в методе выполняния,
 	// чтобы не выбирать её снова
@@ -68,7 +71,7 @@ func (s *ExampleRequestRegister) Validate(req *rest.Request) *rest.Response {
 	return nil
 }
 
-func (s *ExampleRequestRegister) Execute(req *rest.Request) *rest.Response {
+func (s *ExampleRequestRegister) Execute(req rest.IRequest) rest.IResponse {
 	// создаем нового юзера
 	userID := App.GenerateID()
 	user := &ExampleUser{
@@ -86,7 +89,7 @@ func (s *ExampleRequestRegister) Execute(req *rest.Request) *rest.Response {
 		0,
 	)
 	if err != nil {
-		return rest.ResponseErrorMessage("TokenGenerateError", err.Error(), 500)
+		return req.ResponseError(500, rest.ErrorMessage("TokenGenerateError", err.Error()))
 	}
 
 	files := make(map[string]io.ReadCloser)
@@ -94,11 +97,11 @@ func (s *ExampleRequestRegister) Execute(req *rest.Request) *rest.Response {
 	App.users.Store(userID, user)
 	fields, err := rest.Fields(user, files, req.Data().Slice("fields", nil)...)
 	if err != nil {
-		return rest.ResponseErrorMessage("FieldsError", err.Error(), 500)
+		return req.ResponseError(500, rest.ErrorMessage("FieldsError", err.Error()))
 	}
 
 	// возвращаем успешный ответ
-	return rest.ResponseSuccess(
+	return req.ResponseSuccess(
 		json.Map{
 			"user":  fields,
 			"token": token,
@@ -113,29 +116,29 @@ type ExampleRequestUserInfo struct {
 	user *ExampleUser
 }
 
-func (s *ExampleRequestUserInfo) Validate(req *rest.Request) *rest.Response {
+func (s *ExampleRequestUserInfo) Validate(req rest.IRequest) rest.IResponse {
 	if !req.IsAuth() {
-		return rest.ResponseErrorMessage("NotAuth", "Not authorized", 500)
+		return req.ResponseError(500, rest.ErrorMessage("NotAuth", "Not authorized"))
 	}
 	auth := req.Auth()
 	user, check := App.users.Load(auth.Int("id", 0))
 	if !check {
-		return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
+		return req.ResponseError(500, rest.ErrorMessage("UserNotFound", "User not found"))
 	}
 	s.user = user.(*ExampleUser)
 	return nil
 }
 
-func (s *ExampleRequestUserInfo) Execute(req *rest.Request) *rest.Response {
+func (s *ExampleRequestUserInfo) Execute(req rest.IRequest) rest.IResponse {
 	files := make(map[string]io.ReadCloser)
 	log.Println(s.user.Group)
 	fields := req.Data().Slice("fields", nil)
 	rFields, err := rest.Fields(s.user, files, fields...)
 	log.Println(err)
 	if err != nil {
-		return rest.ResponseError(err)
+		return req.ResponseError(500, err)
 	}
-	return rest.ResponseSuccess(rFields, files)
+	return req.ResponseSuccess(rFields, files)
 }
 
 // set avatar
@@ -145,32 +148,32 @@ type ExampleRequestSetAvatar struct {
 	avatar io.Reader
 }
 
-func (s *ExampleRequestSetAvatar) Validate(req *rest.Request) *rest.Response {
-	log.Println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+func (s *ExampleRequestSetAvatar) Validate(req rest.IRequest) rest.IResponse {
 	// проверяем авторизацию
 	if !req.IsAuth() {
-		return rest.ResponseErrorMessage("NotAuth", "Not authorized", 500)
+		return req.ResponseError(500, rest.ErrorMessage("NotAuth", "Not authorized"))
 	}
 	// проверяем файл аватара
 	var check bool
 	if s.avatar, check = req.File("avatar.jpg"); !check {
-		return rest.ResponseErrorMessage("FileNotFound", "avatar.jpg", 500)
+		return req.ResponseError(500, rest.ErrorMessage("FileNotFound", "avatar.jpg"))
 	}
 	// поиск юзера
 	auth := req.Auth()
 	user, check := App.users.Load(auth.Int("id", 0))
 	if !check {
-		return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
+		//return rest.ResponseErrorMessage("UserNotFound", "User not found", 500)
+		return req.ResponseError(500, rest.ErrorMessage("UserNotFound", "User not found"))
 	}
 	s.user = user.(*ExampleUser)
 	return nil
 }
 
-func (s *ExampleRequestSetAvatar) Execute(req *rest.Request) *rest.Response {
+func (s *ExampleRequestSetAvatar) Execute(req rest.IRequest) rest.IResponse {
 	// сохраняем аватар
 	f, _ := req.File("avatar.jpg")
 	s.user.Avatar, _ = io.ReadAll(f)
 	log.Println("U_Ava", s.user.Avatar)
 	// возвращаем пустой ответ
-	return rest.ResponseSuccess(json.Map{}, nil)
+	return req.ResponseSuccess(json.Map{}, nil)
 }

+ 1 - 1
fielder.go

@@ -181,7 +181,7 @@ func Fields(obj any, files map[string]io.ReadCloser, names ...any) (json.Map, IE
 	}
 	res, check := rVal.Interface().(json.Map)
 	if !check {
-		return nil, NewErrorMessage("ErrFields", "Empty object")
+		return nil, ErrorMessage("ErrFields", "Empty object")
 	}
 	return res, nil
 }

+ 11 - 65
request.go

@@ -2,73 +2,19 @@ package rest
 
 import (
 	"io"
-	"net/http"
 
 	"git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
-// Request реализует объект запроса
-type Request struct {
-	*http.Request
-	auth           json.Map
-	data           json.Map
-	files          map[string]io.ReadCloser
-	tokenGenerator func(json.Map, int64) (string, error)
-}
-
-// GenerateToken создает новый токен авторизации. expire - timestamp даты, после которой токен не будет действителен (если указан 0 - токен бессрочный)
-func (s *Request) GenerateToken(data json.Map, expire int64) (string, error) {
-	return s.tokenGenerator(data, expire)
-}
-
-// Возвращает путь (краткий url) запроса
-func (s *Request) RPath() string {
-	return s.URL.Path
-}
-
-// Data возвращает словерь json запроса
-func (s *Request) Data() json.Map {
-	return s.data
-}
-
-// FileKeys возвращает все имена файлы, которые доступны в запросе
-func (s *Request) FileKeys() []string {
-	res := make([]string, 0, len(s.files))
-	for k := range s.files {
-		res = append(res, k)
-	}
-	return res
-}
-
-// File возвращает файл, принятый в запросе multipart/form-data, если он существует
-func (s *Request) File(name string) (io.Reader, bool) {
-	r, check := s.files[name]
-	return r, check
-}
-
-// Auth возвращает словарь с данными об авторизации (при условии, что в заголовке Bearer указан токен и его удалось успешно расшифровать и распарсить)
-func (s *Request) Auth() json.Map {
-	return s.auth
-}
-
-// IsAuth указывает, есть ли словарь авторизации или нет
-func (s *Request) IsAuth() bool {
-	return s.auth != nil
-}
-
-// IsJSON возарвщвет true, если в заголовке ContentType application/json
-func (s *Request) IsJSON() bool {
-	return s.Header.Get("Content-Type") == "application/json"
-}
-
-// // IsForm возарвщвет true, если в заголовке ContentType multipart/form-data
-func (s *Request) IsForm() bool {
-	return s.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
-}
-
-// Close закрывает все открытые ресурсы запоса (файлы)
-func (s *Request) Close() {
-	for _, file := range s.files {
-		file.Close()
-	}
+type IRequest interface {
+	IsAuth() bool
+	Command() string
+	Auth() json.Map
+	Data() json.Map
+	FileKeys() []string
+	File(name string) (io.Reader, bool)
+	GenerateToken(data json.Map, expire int64) (string, error)
+	Root() any
+	ResponseSuccess(data json.Map, files map[string]io.ReadCloser) IResponse
+	ResponseError(code int, err IErrorArgs) IResponse
 }

+ 6 - 146
response.go

@@ -1,153 +1,13 @@
 package rest
 
 import (
-	"bytes"
-	"encoding/json"
 	"io"
-	"log"
-	"mime/multipart"
-	"net/http"
-	"net/textproto"
-
-	mjson "git.ali33.ru/fcg-xvii/go-tools/json"
 )
 
-func NewResponse() *Response {
-	return &Response{
-		data:  make(mjson.Map),
-		files: make(map[string]io.ReadCloser),
-	}
-}
-
-// Response реализует объект ответа
-type Response struct {
-	data  mjson.Map
-	files map[string]io.ReadCloser
-	err   IErrorArgs
-	code  int
-}
-
-func (s *Response) IsError() bool {
-	return s.err != nil
-}
-
-// KeySet устанавливает значение в словаре ответа по ключу
-func (s *Response) KeySet(key string, val any) {
-	s.data[key] = val
-}
-
-// FileSet устанавливает файл в словаре файлов по ключу
-func (s *Response) FileSet(name string, file io.ReadCloser) {
-	s.files[name] = file
-}
-
-// Close закрывает ресурсы ответа после завершения отдачи серверу
-func (s *Response) Close() {
-	for _, file := range s.files {
-		file.Close()
-	}
-}
-
-// Send отправляет запрос серверу
-func (s *Response) Send(w http.ResponseWriter) IErrorArgs {
-	log.Println("SEND...")
-	defer s.Close()
-	if s.IsError() {
-		w.Header().Set("Content-Type", "application/json")
-		w.WriteHeader(s.code)
-		w.Write([]byte(s.err.Error()))
-		return nil
-	}
-	// Если есть файлы, то используем multipart
-	if len(s.files) > 0 {
-		var b bytes.Buffer
-		writer := multipart.NewWriter(&b)
-		w.Header().Set("Content-Type", writer.FormDataContentType())
-
-		// Добавляем JSON-данные как часть
-		partHeader := make(textproto.MIMEHeader)
-		partHeader.Set("Content-Type", "application/json")
-		partHeader.Set("Content-Disposition", `form-data; name="data"`)
-		dataPart, err := writer.CreatePart(partHeader)
-		if err != nil {
-			return NewErrorMessage("ErrResponsePartDataCreate", err.Error())
-		}
-		if err := json.NewEncoder(dataPart).Encode(s.data); err != nil {
-			return NewErrorMessage("ErrResponseDataJsonEncode", err.Error())
-		}
-
-		// Добавляем файлы
-		for filename, file := range s.files {
-			part, err := writer.CreateFormFile("file", filename)
-			if err != nil {
-				return NewErrorMessage("ErrResponsePartFileCreate", err.Error())
-			}
-			if _, err := io.Copy(part, file); err != nil {
-				return NewErrorMessage("ErrResponsePartFileCopy", err.Error())
-			}
-		}
-
-		// Закрываем multipart writer
-		if err := writer.Close(); err != nil {
-			return NewErrorMessage("ErrResponsePartFileClose", err.Error())
-		}
-
-		// Отправляем multipart response
-		w.Header().Set("Content-Type", writer.FormDataContentType())
-		w.Write(b.Bytes())
-	} else {
-		// Если нет файлов, просто отправляем JSON
-		w.Header().Set("Content-Type", "application/json")
-		if err := json.NewEncoder(w).Encode(s.data); err != nil {
-			return NewErrorMessage("ErrResponseDataJsonEncode", err.Error())
-		}
-	}
-
-	return nil
-}
-
-// Успрешный ответ
-
-func ResponseSuccess(data mjson.Map, files map[string]io.ReadCloser) *Response {
-	return &Response{
-		code:  200,
-		data:  data,
-		files: files,
-	}
-}
-
-// Конструкторы ошибок /////////////////////////////////////////
-
-func ResponseErrorArgs(err IErrorArgs, code int) *Response {
-	return &Response{
-		code: code,
-		err:  err,
-	}
-}
-
-func ResponseErrorMessage(err, message string, code int) *Response {
-	return &Response{
-		code: code,
-		err:  NewErrorMessage(err, message),
-	}
-}
-
-///////////////////////////////////////////
-
-func ResponseError(err IErrorArgs) *Response {
-	return &Response{
-		code: 500,
-		err:  err,
-	}
-}
-
-func ResponseErrField(field, message string) *Response {
-	return ResponseError(ErrorFiled(field, message))
-}
-
-func ResponseNotFound(path string) *Response {
-	return &Response{
-		code: 404,
-		err:  NewErrorMessage("ErrNotFound", path),
-	}
+type IResponse interface {
+	IsError() bool
+	KeySet(key string, val any)
+	FileSet(name string, file io.ReadCloser)
+	Close()
+	Send(writer any) IErrorArgs
 }

+ 1 - 0
rest_http/error.go

@@ -0,0 +1 @@
+package rest_http

+ 87 - 0
rest_http/request.go

@@ -0,0 +1,87 @@
+package rest_http
+
+import (
+	"io"
+	"net/http"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+// Request реализует объект запроса
+type Request struct {
+	*http.Request
+	auth           json.Map
+	data           json.Map
+	files          map[string]io.ReadCloser
+	tokenGenerator func(json.Map, int64) (string, error)
+}
+
+func (s *Request) Root() any {
+	return s.Request
+}
+
+// GenerateToken создает новый токен авторизации. expire - timestamp даты, после которой токен не будет действителен (если указан 0 - токен бессрочный)
+func (s *Request) GenerateToken(data json.Map, expire int64) (string, error) {
+	return s.tokenGenerator(data, expire)
+}
+
+// Возвращает путь (краткий url) запроса
+func (s *Request) Command() string {
+	return s.URL.Path
+}
+
+// Data возвращает словерь json запроса
+func (s *Request) Data() json.Map {
+	return s.data
+}
+
+// FileKeys возвращает все имена файлы, которые доступны в запросе
+func (s *Request) FileKeys() []string {
+	res := make([]string, 0, len(s.files))
+	for k := range s.files {
+		res = append(res, k)
+	}
+	return res
+}
+
+// File возвращает файл, принятый в запросе multipart/form-data, если он существует
+func (s *Request) File(name string) (io.Reader, bool) {
+	r, check := s.files[name]
+	return r, check
+}
+
+// Auth возвращает словарь с данными об авторизации (при условии, что в заголовке Bearer указан токен и его удалось успешно расшифровать и распарсить)
+func (s *Request) Auth() json.Map {
+	return s.auth
+}
+
+// IsAuth указывает, есть ли словарь авторизации или нет
+func (s *Request) IsAuth() bool {
+	return s.auth != nil
+}
+
+// IsJSON возарвщвет true, если в заголовке ContentType application/json
+func (s *Request) IsJSON() bool {
+	return s.Header.Get("Content-Type") == "application/json"
+}
+
+// // IsForm возарвщвет true, если в заголовке ContentType multipart/form-data
+func (s *Request) IsForm() bool {
+	return s.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
+}
+
+// Close закрывает все открытые ресурсы запоса (файлы)
+func (s *Request) Close() {
+	for _, file := range s.files {
+		file.Close()
+	}
+}
+
+func (s *Request) ResponseSuccess(data json.Map, files map[string]io.ReadCloser) rest.IResponse {
+	return ResponseSuccess(data, files)
+}
+
+func (s *Request) ResponseError(code int, err rest.IErrorArgs) rest.IResponse {
+	return ResponseError(code, err)
+}

+ 126 - 0
rest_http/response.go

@@ -0,0 +1,126 @@
+package rest_http
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"log"
+	"mime/multipart"
+	"net/http"
+	"net/textproto"
+
+	mjson "git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
+)
+
+func NewResponse() *Response {
+	return &Response{
+		data:  make(mjson.Map),
+		files: make(map[string]io.ReadCloser),
+	}
+}
+
+// Response реализует объект ответа
+type Response struct {
+	data  mjson.Map
+	files map[string]io.ReadCloser
+	err   rest.IErrorArgs
+	code  int
+}
+
+func (s *Response) IsError() bool {
+	return s.err != nil
+}
+
+// KeySet устанавливает значение в словаре ответа по ключу
+func (s *Response) KeySet(key string, val any) {
+	s.data[key] = val
+}
+
+// FileSet устанавливает файл в словаре файлов по ключу
+func (s *Response) FileSet(name string, file io.ReadCloser) {
+	s.files[name] = file
+}
+
+// Close закрывает ресурсы ответа после завершения отдачи серверу
+func (s *Response) Close() {
+	for _, file := range s.files {
+		file.Close()
+	}
+}
+
+// Send отправляет запрос серверу
+func (s *Response) Send(writer any) rest.IErrorArgs {
+	w := writer.(http.ResponseWriter)
+	log.Println("SEND...")
+	defer s.Close()
+	if s.IsError() {
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(s.code)
+		w.Write([]byte(s.err.Error()))
+		return nil
+	}
+	// Если есть файлы, то используем multipart
+	if len(s.files) > 0 {
+		var b bytes.Buffer
+		writer := multipart.NewWriter(&b)
+		w.Header().Set("Content-Type", writer.FormDataContentType())
+
+		// Добавляем JSON-данные как часть
+		partHeader := make(textproto.MIMEHeader)
+		partHeader.Set("Content-Type", "application/json")
+		partHeader.Set("Content-Disposition", `form-data; name="data"`)
+		dataPart, err := writer.CreatePart(partHeader)
+		if err != nil {
+			return rest.ErrorMessage("ErrResponsePartDataCreate", err.Error())
+		}
+		if err := json.NewEncoder(dataPart).Encode(s.data); err != nil {
+			return rest.ErrorMessage("ErrResponseDataJsonEncode", err.Error())
+		}
+
+		// Добавляем файлы
+		for filename, file := range s.files {
+			part, err := writer.CreateFormFile("file", filename)
+			if err != nil {
+				return rest.ErrorMessage("ErrResponsePartFileCreate", err.Error())
+			}
+			if _, err := io.Copy(part, file); err != nil {
+				return rest.ErrorMessage("ErrResponsePartFileCopy", err.Error())
+			}
+		}
+
+		// Закрываем multipart writer
+		if err := writer.Close(); err != nil {
+			return rest.ErrorMessage("ErrResponsePartFileClose", err.Error())
+		}
+
+		// Отправляем multipart response
+		w.Header().Set("Content-Type", writer.FormDataContentType())
+		w.Write(b.Bytes())
+	} else {
+		// Если нет файлов, просто отправляем JSON
+		w.Header().Set("Content-Type", "application/json")
+		if err := json.NewEncoder(w).Encode(s.data); err != nil {
+			return rest.ErrorMessage("ErrResponseDataJsonEncode", err.Error())
+		}
+	}
+
+	return nil
+}
+
+// Успрешный ответ
+
+func ResponseSuccess(data mjson.Map, files map[string]io.ReadCloser) *Response {
+	return &Response{
+		code:  200,
+		data:  data,
+		files: files,
+	}
+}
+
+func ResponseError(code int, err rest.IErrorArgs) *Response {
+	return &Response{
+		code: code,
+		err:  err,
+	}
+}

+ 12 - 17
rest.go → rest_http/rest.go

@@ -1,4 +1,4 @@
-package rest
+package rest_http
 
 import (
 	"bytes"
@@ -14,10 +14,11 @@ import (
 	"time"
 
 	mjson "git.ali33.ru/fcg-xvii/go-tools/json"
+	"git.ali33.ru/fcg-xvii/rest"
 	jwt "github.com/dgrijalva/jwt-go"
 )
 
-func New(app IApplication) *Rest {
+func New(app rest.IApplication) *Rest {
 	return &Rest{
 		app: app,
 	}
@@ -26,10 +27,10 @@ func New(app IApplication) *Rest {
 type Rest struct {
 	opened atomic.Bool
 	server *http.Server
-	app    IApplication
+	app    rest.IApplication
 }
 
-func (s *Rest) App() IApplication {
+func (s *Rest) App() rest.IApplication {
 	return s.app
 }
 
@@ -80,14 +81,13 @@ func responseNotFound(w http.ResponseWriter) {
 	w.WriteHeader(404)
 }
 
-func responseError(w http.ResponseWriter, err IErrorArgs, code int) {
+func responseError(w http.ResponseWriter, err rest.IErrorArgs, code int) {
 	w.WriteHeader(code)
 	w.Write([]byte(err.Error()))
 }
 
 // handle
 func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
-	log.Println("handle", r.URL.Path)
 	// Инициализация restRequest
 	rr := &Request{
 		Request:        r,
@@ -98,7 +98,6 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 	// Парсим Bearer токен и извлекаем claims
 	authHeader := r.Header.Get("Authorization")
-	log.Println("AUTHHEAD", authHeader)
 	if authHeader != "" {
 		if parts := strings.Split(authHeader, " "); len(parts) == 2 && parts[0] == "Bearer" {
 			tokenString := parts[1]
@@ -121,14 +120,12 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	log.Println(r.Header.Get("Content-Type"))
-
 	// Если это многокомпонентный запрос, обрабатываем файлы
 	if strings.Index(r.Header.Get("Content-Type"), "multipart/form-data") == 0 {
 		err := r.ParseMultipartForm(32 << 20) // max memory 32MB, после этого файлы будут сохранены во временных файлах
 		log.Println(err)
 		if err != nil {
-			responseError(w, NewErrorMessage("ErrMultipartParse", err.Error()), 500)
+			responseError(w, rest.ErrorMessage("ErrMultipartParse", err.Error()), 500)
 			return
 		}
 		multiPartForm := r.MultipartForm
@@ -137,7 +134,7 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 		if check {
 			err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rr.data)
 			if err != nil {
-				responseError(w, NewErrorMessage("ErrMultipartDataParse", err.Error()), 500)
+				responseError(w, rest.ErrorMessage("ErrMultipartDataParse", err.Error()), 500)
 				return
 			}
 		}
@@ -146,7 +143,7 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 			for _, header := range headers {
 				file, err := header.Open()
 				if err != nil {
-					err := NewError(
+					err := rest.NewError(
 						"ErrMultipartFileParse",
 						mjson.Map{
 							"filename": filename,
@@ -162,13 +159,11 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 	} else {
 		err := json.NewDecoder(r.Body).Decode(&rr.data)
 		if err != nil {
-			responseError(w, NewErrorMessage("ErrDataParse", err.Error()), 500)
+			responseError(w, rest.ErrorMessage("ErrDataParse", err.Error()), 500)
 			return
 		}
 	}
 
-	log.Println("RR", rr)
-
 	// get command
 
 	//command, check := s.commands.GetCommand(r.URL.Path)
@@ -180,13 +175,13 @@ func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
 
 	log.Println(rr.data)
 	// serialize
-	if err := serialize(rr.data, command); err != nil {
+	if err := rest.Serialize(rr.data, command); err != nil {
 		responseError(w, err, 500)
 		return
 	}
 
 	// validate
-	if validator, check := command.(IValidator); check {
+	if validator, check := command.(rest.IValidator); check {
 		resp := validator.Validate(rr)
 		if resp != nil {
 			if err := resp.Send(w); err != nil {

+ 26 - 6
serialize.go

@@ -13,7 +13,14 @@ func parseVal(from, to reflect.Value, fieldName string) IErrorArgs {
 		from = from.Elem()
 	}
 	if !from.CanConvert(to.Type()) {
-		return NewErrFieldType(fieldName, from.Type().String(), to.Type().String(), "unexpected type", -1)
+		return NewError(
+			"FieldType",
+			json.Map{
+				"field":    fieldName,
+				"expected": to.Type().String(),
+				"received": from.Type().String(),
+			},
+		)
 	}
 	to.Set(from.Convert(to.Type()))
 	return nil
@@ -91,12 +98,18 @@ func parseStruct(from, to reflect.Value, fieldName string) IErrorArgs {
 				rVal = from.MapIndex(reflect.ValueOf(camelToSnake(fType.Name)))
 			}
 		default:
-			return NewErrFieldType(fieldName, from.Type().String(), to.Type().String(), "unexpected type", -1)
-
+			return NewError(
+				"FieldType",
+				json.Map{
+					"field":    fieldName,
+					"expected": to.Type().String(),
+					"received": from.Type().String(),
+				},
+			)
 		}
 		if !rVal.IsValid() {
 			if required {
-				return NewErrorMessage("RequiredField", fType.Name)
+				return ErrorMessage("RequiredField", fType.Name)
 			}
 			continue
 		}
@@ -138,12 +151,19 @@ func parseMap(from, to reflect.Value, fieldName string) IErrorArgs {
 			to.SetMapIndex(reflect.ValueOf(fName), toVal)
 		}
 	default:
-		return NewErrFieldType(fieldName, from.Type().String(), to.Type().String(), "unexpected type", -1)
+		return NewError(
+			"FieldType",
+			json.Map{
+				"field":    fieldName,
+				"expected": to.Type().String(),
+				"received": from.Type().String(),
+			},
+		)
 	}
 	return nil
 }
 
-func serialize(from json.Map, to any) IErrorArgs {
+func Serialize(from json.Map, to any) IErrorArgs {
 	elemTo := reflect.ValueOf(to).Elem()
 	return parseType(
 		reflect.ValueOf(&from),