Browse Source

feat: standardize error response (#48)

* feat: update openapi

* feat: standardize error
pull/49/head v4.4.1
Aldino Kemal 3 years ago
committed by GitHub
parent
commit
96d43b5942
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 185
      docs/openapi.yaml
  2. 1
      src/go.mod
  3. 3
      src/go.sum
  4. 6
      src/internal/rest/app.go
  5. 3
      src/internal/rest/middleware/recovery.go
  6. 9
      src/internal/rest/send.go
  7. 6
      src/internal/rest/user.go
  8. 108
      src/pkg/error/app_error.go
  9. 19
      src/pkg/error/auth_error.go
  10. 18
      src/pkg/error/whatsapp_error.go
  11. 4
      src/pkg/utils/response.go
  12. 23
      src/services/app.go

185
docs/openapi.yaml

@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: WhatsApp API MultiDevice
version: 3.3.0
version: 3.3.1
description: This API is used for sending whatsapp via API
servers:
- url: http://localhost:3000
@ -85,54 +85,10 @@ paths:
responses:
'200':
description: OK
headers:
Date:
schema:
type: string
example: Sun, 13 Feb 2022 05:46:55 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '394'
Vary:
schema:
type: string
example: Origin
Access-Control-Allow-Origin:
schema:
type: string
example: '*'
content:
application/json:
schema:
type: object
example:
code: 200
message: Success
results:
verified_name: ''
status: you are blocked
picture_id: '1635239861'
devices:
- User: '6289685028129@s.whatsapp.net'
Agent: 0
Device: UNKNOWN
Server: s.whatsapp.net
AD: true
- User: '6289685028129@s.whatsapp.net'
Agent: 0
Device: SAFARI
Server: s.whatsapp.net
AD: true
- User: '6289685028129@s.whatsapp.net'
Agent: 0
Device: IPAD
Server: s.whatsapp.net
AD: true
$ref: '#/components/schemas/UserInfoResponse'
'400':
description: Bad Request
content:
@ -190,40 +146,10 @@ paths:
responses:
'200':
description: OK
headers:
Date:
schema:
type: string
example: Sun, 13 Feb 2022 04:01:41 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '147'
Vary:
schema:
type: string
example: Origin
Access-Control-Allow-Origin:
schema:
type: string
example: '*'
content:
application/json:
schema:
type: object
example:
code: 200
message: Success get privacy
results:
group_add: all
last_seen: ''
status: all
profile: contacts
read_receipts: all
$ref: '#/components/schemas/UserPrivacyResponse'
'500':
description: Internal Server Error
content:
@ -601,6 +527,9 @@ components:
UserGroupResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success get list groups
@ -638,9 +567,53 @@ components:
type: boolean
Error:
type: number
UserInfoResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example:
results:
type: object
properties:
verified_name:
type: string
example: Aldino Kemal
status:
type: string
example: Hello World
picture_id:
type: string
example: 1651459152
devices:
type: array
items:
type: object
properties:
User:
type: string
example: 6289685021291
Agent:
type: integer
example: 0
Device:
type: string
example: UNKNOWN
Server:
type: string
example: s.whatsapp.net
AD:
type: boolean
example: true
UserAvatarResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success
@ -656,39 +629,57 @@ components:
type:
type: string
example: 'image'
SendResponse:
UserPrivacyResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success
example: Success get privacy
results:
type: object
properties:
message_id:
group_add:
type: string
example: '3EB0B430B6F8F1D0E053AC120E0A9E5C'
example: all
last_seen:
type: string
example: null
status:
type: string
example: '<feature> success ....'
GenericResponse:
example: all
profile:
type: string
example: all
read_receipts:
type: string
example: all
SendResponse:
type: object
properties:
code:
type: integer
example: 200
type: string
example: SUCCESS
message:
type: string
example: Success
results:
type: object
properties:
message_id:
type: string
example: null
example: '3EB0B430B6F8F1D0E053AC120E0A9E5C'
status:
type: string
example: '<feature> success ....'
LoginResponse:
type: object
properties:
code:
type: integer
example: 200
type: string
example: SUCCESS
message:
type: string
example: Success
@ -701,13 +692,25 @@ components:
qr_link:
type: string
example: 'http://localhost:3000/statics/images/qrcode/scan-qr-b0b7bb43-9a22-455a-814f-5a225c743310.png'
GenericResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success
results:
type: string
example: null
ErrorInternalServer:
type: object
properties:
code:
type: integer
example: 500
description: 'HTTP Status Code'
type: string
example: INTERNAL_SERVER_ERROR
description: 'SYSTEM_CODE_ERROR'
message:
type: string
example: you are not loggin

1
src/go.mod

@ -35,6 +35,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect

3
src/go.sum

@ -365,6 +365,8 @@ github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJv
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -617,6 +619,7 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=

6
src/internal/rest/app.go

@ -27,7 +27,8 @@ func (handler *App) Login(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Message: "Success",
Code: "SUCCESS",
Message: "Login success",
Results: map[string]any{
"qr_link": fmt.Sprintf("%s://%s/%s", c.Protocol(), c.Hostname(), response.ImagePath),
"qr_duration": response.Duration,
@ -41,6 +42,7 @@ func (handler *App) Logout(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success logout",
Results: nil,
})
@ -52,6 +54,7 @@ func (handler *App) Reconnect(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Reconnect success",
Results: nil,
})
@ -63,6 +66,7 @@ func (handler *App) Devices(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Fetch device success",
Results: devices,
})

3
src/internal/rest/middleware/recovery.go

@ -14,7 +14,8 @@ func Recovery() fiber.Handler {
if err != nil {
var res utils.ResponseData
res.Status = 500
res.Message = fmt.Sprintf("%s", err)
res.Code = "INTERNAL_SERVER_ERROR"
res.Message = fmt.Sprintf("%v", err)
errValidation, isValidationError := err.(pkgError.GenericError)
if isValidationError {

9
src/internal/rest/send.go

@ -37,6 +37,7 @@ func (controller *Send) SendText(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -60,6 +61,7 @@ func (controller *Send) SendImage(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -81,6 +83,7 @@ func (controller *Send) SendFile(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -102,6 +105,7 @@ func (controller *Send) SendVideo(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -119,6 +123,7 @@ func (controller *Send) SendContact(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -136,6 +141,7 @@ func (controller *Send) SendLink(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -153,6 +159,7 @@ func (controller *Send) SendLocation(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -171,6 +178,7 @@ func (controller *Send) RevokeMessage(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})
@ -189,6 +197,7 @@ func (controller *Send) UpdateMessage(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: response.Status,
Results: response,
})

6
src/internal/rest/user.go

@ -33,7 +33,8 @@ func (controller *User) UserInfo(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Message: "Success",
Code: "SUCCESS",
Message: "Success get user info",
Results: response.Data[0],
})
}
@ -50,6 +51,7 @@ func (controller *User) UserAvatar(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success get avatar",
Results: response,
})
@ -61,6 +63,7 @@ func (controller *User) UserMyPrivacySetting(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success get privacy",
Results: response,
})
@ -72,6 +75,7 @@ func (controller *User) UserMyListGroups(c *fiber.Ctx) error {
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success get list groups",
Results: response,
})

108
src/pkg/error/app_error.go

@ -0,0 +1,108 @@
package error
import "net/http"
type LoginError string
// Error for complying the error interface
func (e LoginError) Error() string {
return string(e)
}
// ErrCode will return the error code based on the error data type
func (e LoginError) ErrCode() string {
return "ALREADY_LOGGED_IN"
}
// StatusCode will return the HTTP status code based on the error data type
func (e LoginError) StatusCode() int {
return http.StatusBadRequest
}
type ReconnectError string
func throwReconnectError(text string) GenericError {
return AuthError(text)
}
// Error for complying the error interface
func (e ReconnectError) Error() string {
return string(e)
}
// ErrCode will return the error code based on the error data type
func (e ReconnectError) ErrCode() string {
return "RECONNECT_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (e ReconnectError) StatusCode() int {
return http.StatusBadRequest
}
type AuthError string
func throwAuthError(text string) GenericError {
return AuthError(text)
}
func (err AuthError) Error() string {
return string(err)
}
// ErrCode will return the error code based on the error data type
func (err AuthError) ErrCode() string {
return "AUTHENTICATION_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (err AuthError) StatusCode() int {
return http.StatusUnauthorized
}
type qrChannelError string
func throwQrChannelError(text string) GenericError {
return qrChannelError(text)
}
func (err qrChannelError) Error() string {
return string(err)
}
// ErrCode will return the error code based on the error data type
func (err qrChannelError) ErrCode() string {
return "QR_CHANNEL_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (err qrChannelError) StatusCode() int {
return http.StatusInternalServerError
}
type sessionSavedError string
func throwSessionSavedError(text string) GenericError {
return sessionSavedError(text)
}
func (err sessionSavedError) Error() string {
return string(err)
}
// ErrCode will return the error code based on the error data type
func (err sessionSavedError) ErrCode() string {
return "SESSION_SAVED_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (err sessionSavedError) StatusCode() int {
return http.StatusInternalServerError
}
var (
ErrAlreadyLoggedIn = LoginError("You already logged in :)")
ErrReconnect = throwReconnectError("Reconnect error")
ErrQrChannel = throwQrChannelError("QR channel error")
ErrorSessionSaved = throwSessionSavedError("Your session have been saved, please wait to connect 2 second and refresh again")
)

19
src/pkg/error/auth_error.go

@ -1,19 +0,0 @@
package error
import "net/http"
type AuthError string
func (err AuthError) Error() string {
return string(err)
}
// ErrCode will return the error code based on the error data type
func (err AuthError) ErrCode() string {
return "AUTHENTICATION_ERROR"
}
// StatusCode will return the HTTP status code based on the error data type
func (err AuthError) StatusCode() int {
return http.StatusUnauthorized
}

18
src/pkg/error/whatsapp_error.go

@ -36,6 +36,24 @@ func (e WebhookError) StatusCode() int {
return http.StatusInternalServerError
}
type WaCliError string
// Error for complying the error interface
func (e WaCliError) Error() string {
return string(e)
}
// ErrCode will return the error code based on the error data type
func (e WaCliError) ErrCode() string {
return "INVALID_WA_CLI"
}
// StatusCode will return the HTTP status code based on the error data type
func (e WaCliError) StatusCode() int {
return http.StatusInternalServerError
}
const (
ErrInvalidJID = InvalidJID("your JID is invalid")
ErrWaCLI = WaCliError("your WhatsApp CLI is invalid or empty")
)

4
src/pkg/utils/response.go

@ -2,7 +2,7 @@ package utils
type ResponseData struct {
Status int `json:"-"`
Code string `json:"code,omitempty"`
Code string `json:"code"`
Message string `json:"message"`
Results any `json:"results"`
Results any `json:"results,omitempty"`
}

23
src/services/app.go

@ -6,8 +6,11 @@ import (
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/sirupsen/logrus"
"github.com/skip2/go-qrcode"
"go.mau.fi/libsignal/logger"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/store/sqlstore"
"os"
@ -30,7 +33,7 @@ func NewAppService(waCli *whatsmeow.Client, db *sqlstore.Container) domainApp.IA
func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResponse, err error) {
if service.WaCli == nil {
return response, errors.New("wa cli nil cok")
return response, pkgError.ErrWaCLI
}
// Disconnect for reconnecting
@ -40,15 +43,16 @@ func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResp
ch, err := service.WaCli.GetQRChannel(context.Background())
if err != nil {
logrus.Error(err.Error())
// This error means that we're already logged in, so ignore it.
if errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
_ = service.WaCli.Connect() // just connect to websocket
if service.WaCli.IsLoggedIn() {
return response, errors.New("you already logged in :)")
return response, pkgError.ErrAlreadyLoggedIn
}
return response, errors.New("your session have been saved, please wait to connect 2 second and refresh again")
return response, pkgError.ErrorSessionSaved
} else {
return response, errors.New("Error when GetQRChannel:" + err.Error())
return response, pkgError.ErrQrChannel
}
} else {
go func() {
@ -59,18 +63,18 @@ func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResp
qrPath := fmt.Sprintf("%s/scan-qr-%s.png", config.PathQrCode, fiberUtils.UUIDv4())
err = qrcode.WriteFile(evt.Code, qrcode.Medium, 512, qrPath)
if err != nil {
fmt.Println("error when write qrImage file", err.Error())
logrus.Error("Error when write qr code to file: ", err)
}
go func() {
time.Sleep(response.Duration * time.Second)
err := os.Remove(qrPath)
if err != nil {
fmt.Println("Failed to remove qrPath " + qrPath)
logrus.Error("error when remove qrImage file", err.Error())
}
}()
chImage <- qrPath
} else {
fmt.Printf("QR channel result: %s", evt.Event)
logrus.Error("error when get qrCode", evt.Event)
}
}
}()
@ -78,7 +82,8 @@ func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResp
err = service.WaCli.Connect()
if err != nil {
return response, errors.New("Failed to connect bro " + err.Error())
logger.Error("Error when connect to whatsapp", err)
return response, pkgError.ErrReconnect
}
response.ImagePath = <-chImage
@ -137,7 +142,7 @@ func (service serviceApp) Reconnect(_ context.Context) (err error) {
func (service serviceApp) FetchDevices(_ context.Context) (response []domainApp.FetchDevicesResponse, err error) {
if service.WaCli == nil {
return response, errors.New("wa cli nil cok")
return response, pkgError.ErrWaCLI
}
devices, err := service.db.GetAllDevices()

Loading…
Cancel
Save