4
0

json.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package json // import "miniflux.app/v2/internal/http/response/json"
  4. import (
  5. "encoding/json"
  6. "errors"
  7. "log/slog"
  8. "net/http"
  9. "miniflux.app/v2/internal/http/request"
  10. "miniflux.app/v2/internal/http/response"
  11. )
  12. const contentTypeHeader = `application/json`
  13. // OK creates a new JSON response with a 200 status code.
  14. func OK(w http.ResponseWriter, r *http.Request, body any) {
  15. responseBody, err := json.Marshal(body)
  16. if err != nil {
  17. ServerError(w, r, err)
  18. return
  19. }
  20. builder := response.New(w, r)
  21. builder.WithHeader("Content-Type", contentTypeHeader)
  22. builder.WithBody(responseBody)
  23. builder.Write()
  24. }
  25. // Created sends a created response to the client.
  26. func Created(w http.ResponseWriter, r *http.Request, body any) {
  27. responseBody, err := json.Marshal(body)
  28. if err != nil {
  29. ServerError(w, r, err)
  30. return
  31. }
  32. builder := response.New(w, r)
  33. builder.WithStatus(http.StatusCreated)
  34. builder.WithHeader("Content-Type", contentTypeHeader)
  35. builder.WithBody(responseBody)
  36. builder.Write()
  37. }
  38. // NoContent sends a no content response to the client.
  39. func NoContent(w http.ResponseWriter, r *http.Request) {
  40. builder := response.New(w, r)
  41. builder.WithStatus(http.StatusNoContent)
  42. builder.WithHeader("Content-Type", contentTypeHeader)
  43. builder.Write()
  44. }
  45. func Accepted(w http.ResponseWriter, r *http.Request) {
  46. builder := response.New(w, r)
  47. builder.WithStatus(http.StatusAccepted)
  48. builder.WithHeader("Content-Type", contentTypeHeader)
  49. builder.Write()
  50. }
  51. // ServerError sends an internal error to the client.
  52. func ServerError(w http.ResponseWriter, r *http.Request, err error) {
  53. slog.Error(http.StatusText(http.StatusInternalServerError),
  54. slog.Any("error", err),
  55. slog.String("client_ip", request.ClientIP(r)),
  56. slog.Group("request",
  57. slog.String("method", r.Method),
  58. slog.String("uri", r.RequestURI),
  59. slog.String("user_agent", r.UserAgent()),
  60. ),
  61. slog.Group("response",
  62. slog.Int("status_code", http.StatusInternalServerError),
  63. ),
  64. )
  65. responseBody, jsonErr := generateJSONError(err)
  66. if jsonErr != nil {
  67. slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
  68. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  69. return
  70. }
  71. builder := response.New(w, r)
  72. builder.WithStatus(http.StatusInternalServerError)
  73. builder.WithHeader("Content-Type", contentTypeHeader)
  74. builder.WithBody(responseBody)
  75. builder.Write()
  76. }
  77. // BadRequest sends a bad request error to the client.
  78. func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
  79. slog.Warn(http.StatusText(http.StatusBadRequest),
  80. slog.Any("error", err),
  81. slog.String("client_ip", request.ClientIP(r)),
  82. slog.Group("request",
  83. slog.String("method", r.Method),
  84. slog.String("uri", r.RequestURI),
  85. slog.String("user_agent", r.UserAgent()),
  86. ),
  87. slog.Group("response",
  88. slog.Int("status_code", http.StatusBadRequest),
  89. ),
  90. )
  91. responseBody, jsonErr := generateJSONError(err)
  92. if jsonErr != nil {
  93. slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
  94. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  95. return
  96. }
  97. builder := response.New(w, r)
  98. builder.WithStatus(http.StatusBadRequest)
  99. builder.WithHeader("Content-Type", contentTypeHeader)
  100. builder.WithBody(responseBody)
  101. builder.Write()
  102. }
  103. // Unauthorized sends a not authorized error to the client.
  104. func Unauthorized(w http.ResponseWriter, r *http.Request) {
  105. slog.Warn(http.StatusText(http.StatusUnauthorized),
  106. slog.String("client_ip", request.ClientIP(r)),
  107. slog.Group("request",
  108. slog.String("method", r.Method),
  109. slog.String("uri", r.RequestURI),
  110. slog.String("user_agent", r.UserAgent()),
  111. ),
  112. slog.Group("response",
  113. slog.Int("status_code", http.StatusUnauthorized),
  114. ),
  115. )
  116. responseBody, jsonErr := generateJSONError(errors.New("access unauthorized"))
  117. if jsonErr != nil {
  118. slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
  119. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  120. return
  121. }
  122. builder := response.New(w, r)
  123. builder.WithStatus(http.StatusUnauthorized)
  124. builder.WithHeader("Content-Type", contentTypeHeader)
  125. builder.WithBody(responseBody)
  126. builder.Write()
  127. }
  128. // Forbidden sends a forbidden error to the client.
  129. func Forbidden(w http.ResponseWriter, r *http.Request) {
  130. slog.Warn(http.StatusText(http.StatusForbidden),
  131. slog.String("client_ip", request.ClientIP(r)),
  132. slog.Group("request",
  133. slog.String("method", r.Method),
  134. slog.String("uri", r.RequestURI),
  135. slog.String("user_agent", r.UserAgent()),
  136. ),
  137. slog.Group("response",
  138. slog.Int("status_code", http.StatusForbidden),
  139. ),
  140. )
  141. responseBody, jsonErr := generateJSONError(errors.New("access forbidden"))
  142. if jsonErr != nil {
  143. slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
  144. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  145. return
  146. }
  147. builder := response.New(w, r)
  148. builder.WithStatus(http.StatusForbidden)
  149. builder.WithHeader("Content-Type", contentTypeHeader)
  150. builder.WithBody(responseBody)
  151. builder.Write()
  152. }
  153. // NotFound sends a page not found error to the client.
  154. func NotFound(w http.ResponseWriter, r *http.Request) {
  155. slog.Warn(http.StatusText(http.StatusNotFound),
  156. slog.String("client_ip", request.ClientIP(r)),
  157. slog.Group("request",
  158. slog.String("method", r.Method),
  159. slog.String("uri", r.RequestURI),
  160. slog.String("user_agent", r.UserAgent()),
  161. ),
  162. slog.Group("response",
  163. slog.Int("status_code", http.StatusNotFound),
  164. ),
  165. )
  166. responseBody, jsonErr := generateJSONError(errors.New("resource not found"))
  167. if jsonErr != nil {
  168. slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
  169. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  170. return
  171. }
  172. builder := response.New(w, r)
  173. builder.WithStatus(http.StatusNotFound)
  174. builder.WithHeader("Content-Type", contentTypeHeader)
  175. builder.WithBody(responseBody)
  176. builder.Write()
  177. }
  178. func generateJSONError(err error) ([]byte, error) {
  179. type errorMsg struct {
  180. ErrorMessage string `json:"error_message"`
  181. }
  182. encodedBody, err := json.Marshal(errorMsg{ErrorMessage: err.Error()})
  183. if err != nil {
  184. return nil, err
  185. }
  186. return encodedBody, nil
  187. }