json.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package response // import "miniflux.app/v2/internal/http/response"
  4. import (
  5. "encoding/json"
  6. "errors"
  7. "log/slog"
  8. "net/http"
  9. "miniflux.app/v2/internal/http/request"
  10. )
  11. const jsonContentTypeHeader = `application/json`
  12. // JSON creates a new JSON response with a 200 status code.
  13. func JSON(w http.ResponseWriter, r *http.Request, body any) {
  14. responseBody, err := json.Marshal(body)
  15. if err != nil {
  16. JSONServerError(w, r, err)
  17. return
  18. }
  19. builder := NewBuilder(w, r)
  20. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  21. builder.WithBodyAsBytes(responseBody)
  22. builder.Write()
  23. }
  24. // JSONCreated sends a created response to the client.
  25. func JSONCreated(w http.ResponseWriter, r *http.Request, body any) {
  26. responseBody, err := json.Marshal(body)
  27. if err != nil {
  28. JSONServerError(w, r, err)
  29. return
  30. }
  31. builder := NewBuilder(w, r)
  32. builder.WithStatus(http.StatusCreated)
  33. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  34. builder.WithBodyAsBytes(responseBody)
  35. builder.Write()
  36. }
  37. // JSONAccepted sends an accepted response to the client.
  38. func JSONAccepted(w http.ResponseWriter, r *http.Request) {
  39. builder := NewBuilder(w, r)
  40. builder.WithStatus(http.StatusAccepted)
  41. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  42. builder.Write()
  43. }
  44. // JSONServerError sends an internal error to the client.
  45. func JSONServerError(w http.ResponseWriter, r *http.Request, err error) {
  46. slog.Error(http.StatusText(http.StatusInternalServerError),
  47. slog.Any("error", err),
  48. slog.String("client_ip", request.ClientIP(r)),
  49. slog.Group("request",
  50. slog.String("method", r.Method),
  51. slog.String("uri", r.RequestURI),
  52. slog.String("user_agent", r.UserAgent()),
  53. ),
  54. slog.Group("response",
  55. slog.Int("status_code", http.StatusInternalServerError),
  56. ),
  57. )
  58. builder := NewBuilder(w, r)
  59. builder.WithStatus(http.StatusInternalServerError)
  60. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  61. builder.WithBodyAsBytes(generateJSONError(err))
  62. builder.Write()
  63. }
  64. // JSONBadRequest sends a bad request error to the client.
  65. func JSONBadRequest(w http.ResponseWriter, r *http.Request, err error) {
  66. slog.Warn(http.StatusText(http.StatusBadRequest),
  67. slog.Any("error", err),
  68. slog.String("client_ip", request.ClientIP(r)),
  69. slog.Group("request",
  70. slog.String("method", r.Method),
  71. slog.String("uri", r.RequestURI),
  72. slog.String("user_agent", r.UserAgent()),
  73. ),
  74. slog.Group("response",
  75. slog.Int("status_code", http.StatusBadRequest),
  76. ),
  77. )
  78. builder := NewBuilder(w, r)
  79. builder.WithStatus(http.StatusBadRequest)
  80. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  81. builder.WithBodyAsBytes(generateJSONError(err))
  82. builder.Write()
  83. }
  84. // JSONUnauthorized sends a not authorized error to the client.
  85. func JSONUnauthorized(w http.ResponseWriter, r *http.Request) {
  86. slog.Warn(http.StatusText(http.StatusUnauthorized),
  87. slog.String("client_ip", request.ClientIP(r)),
  88. slog.Group("request",
  89. slog.String("method", r.Method),
  90. slog.String("uri", r.RequestURI),
  91. slog.String("user_agent", r.UserAgent()),
  92. ),
  93. slog.Group("response",
  94. slog.Int("status_code", http.StatusUnauthorized),
  95. ),
  96. )
  97. builder := NewBuilder(w, r)
  98. builder.WithStatus(http.StatusUnauthorized)
  99. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  100. builder.WithBodyAsBytes(generateJSONError(errors.New("access unauthorized")))
  101. builder.Write()
  102. }
  103. // JSONForbidden sends a forbidden error to the client.
  104. func JSONForbidden(w http.ResponseWriter, r *http.Request) {
  105. slog.Warn(http.StatusText(http.StatusForbidden),
  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.StatusForbidden),
  114. ),
  115. )
  116. builder := NewBuilder(w, r)
  117. builder.WithStatus(http.StatusForbidden)
  118. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  119. builder.WithBodyAsBytes(generateJSONError(errors.New("access forbidden")))
  120. builder.Write()
  121. }
  122. // JSONNotFound sends a page not found error to the client.
  123. func JSONNotFound(w http.ResponseWriter, r *http.Request) {
  124. slog.Warn(http.StatusText(http.StatusNotFound),
  125. slog.String("client_ip", request.ClientIP(r)),
  126. slog.Group("request",
  127. slog.String("method", r.Method),
  128. slog.String("uri", r.RequestURI),
  129. slog.String("user_agent", r.UserAgent()),
  130. ),
  131. slog.Group("response",
  132. slog.Int("status_code", http.StatusNotFound),
  133. ),
  134. )
  135. builder := NewBuilder(w, r)
  136. builder.WithStatus(http.StatusNotFound)
  137. builder.WithHeader("Content-Type", jsonContentTypeHeader)
  138. builder.WithBodyAsBytes(generateJSONError(errors.New("resource not found")))
  139. builder.Write()
  140. }
  141. func generateJSONError(err error) []byte {
  142. type errorMsg struct {
  143. ErrorMessage string `json:"error_message"`
  144. }
  145. encodedBody, _ := json.Marshal(errorMsg{ErrorMessage: err.Error()})
  146. return encodedBody
  147. }