-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
161 lines (135 loc) · 3.89 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package service
import (
"encoding/json"
"github.com/julienschmidt/httprouter"
"log"
"net/http"
"net/url"
"strings"
)
// NewServer initializes the service with the given Database, and sets up appropriate routes.
func NewServer(db *Database) *Server {
router := httprouter.New()
server := &Server{
router: router,
db: db,
}
server.setupRoutes()
return server
}
// Server contains all that is needed to respond to incoming requests, like a database. Other services like a mail,
// redis, or S3 server could also be added.
type Server struct {
router *httprouter.Router
db *Database
}
// The ServerError type allows errors to provide an appropriate HTTP status code and message. The Server checks for
// this interface when recovering from a panic inside a handler.
type ServerError interface {
HttpStatusCode() int
HttpStatusMessage() string
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.router.ServeHTTP(w, r)
}
func (s *Server) setupRoutes() {
s.router.POST("/contacts", s.AddContact)
s.router.GET("/contacts/:email", s.GetContactByEmail)
// By default the router will handle errors. But the service should always return JSON if possible, so these
// custom handlers are added.
s.router.NotFound = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
writeJSONError(w, http.StatusNotFound, "")
},
)
s.router.HandleMethodNotAllowed = true
s.router.MethodNotAllowed = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
writeJSONError(w, http.StatusMethodNotAllowed, "")
},
)
s.router.PanicHandler = func(w http.ResponseWriter, r *http.Request, e interface{}) {
serverError, ok := e.(ServerError)
if ok {
writeJSONError(w, serverError.HttpStatusCode(), serverError.HttpStatusMessage())
} else {
log.Printf("Panic during request: %v", e)
writeJSONError(w, http.StatusInternalServerError, "")
}
}
}
// AddContact handles HTTP requests to add a Contact.
func (s *Server) AddContact(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var contact Contact
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&contact); err != nil {
writeJSONError(w, http.StatusBadRequest, "Error decoding JSON")
return
}
contactId, err := s.db.AddContact(contact)
if err != nil {
panic(err)
return
}
contact.Id = contactId
writeJSON(
w,
http.StatusCreated,
&ContactResponse{
Contact: &contact,
},
)
}
// AddContact handles HTTP requests to GET a Contact by an email address.
func (s *Server) GetContactByEmail(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
email, err := url.QueryUnescape(ps.ByName("email"))
if err != nil {
writeJSONError(w, http.StatusBadRequest, "Invalid email.")
return
}
email = strings.TrimSpace(email)
if email == "" {
writeJSONError(w, http.StatusBadRequest, "Expected a single email.")
return
}
contact, err := s.db.GetContactByEmail(email)
if err != nil {
writeUnexpectedError(w, err)
} else if contact == nil {
writeJSONNotFound(w)
} else {
writeJSON(
w,
http.StatusOK,
&ContactResponse{
Contact: contact,
},
)
}
}
// ===== JSON HELPERS ==================================================================================================
func writeJSON(w http.ResponseWriter, statusCode int, response interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
encoder := json.NewEncoder(w)
encoder.Encode(response)
}
func writeJSONError(w http.ResponseWriter, statusCode int, message string) {
if message == "" {
message = http.StatusText(statusCode)
}
writeJSON(
w,
statusCode,
&ErrorResponse{
StatusCode: statusCode,
Message: message,
},
)
}
func writeJSONNotFound(w http.ResponseWriter) {
writeJSONError(w, http.StatusNotFound, "")
}
func writeUnexpectedError(w http.ResponseWriter, err error) {
writeJSONError(w, http.StatusInternalServerError, err.Error())
}