An AI assistant wrote the label route, the text for the shop's shelf: GET /products/1/label answers {"label": "Mug (kitchen)"}. It checks err after the query, which is a good idea: when something fails, the route still answers. But look at what it sends back:
app.HandleFunc("GET /products/{id}/label", func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
var name, category string
query := "SELECT name, category FROM products WHERE id = ?"
err := db.QueryRow(query, id).Scan(&name, &category)
if err != nil {
writeJSON(w, 500, map[string]string{"error": err.Error(), "sql": query, "trace": string(debug.Stack())})
return
}
writeJSON(w, 200, map[string]string{"label": name + " (" + category + ")"})
})
That answer goes to whoever made the request. It holds the error's text, the SQL query and the stack trace: the functions, files and line numbers that led to that point. A customer can't do anything with it. An attacker can: table names, column names and your code are a free map for finding the next hole.
The rule: the details go to the log, which only you read, and the visitor gets a short safe message. Here it is on another route:
if err != nil {
log.Printf("order %d failed: %s", orderID, err)
writeJSON(w, 500, map[string]string{"error": "Please try again later"})
return
}
A missing product is not a server error, so it answers 404, not 500: when no row matches, Scan returns sql.ErrNoRows. Your rewrite must give these answers (product 8 is a Gift card whose category is NULL, a bug nobody had found yet):
GET /products/1/label → 200 {"label": "Mug (kitchen)"}
GET /products/6/label → 200 {"label": "Teapot (kitchen)"}
GET /products/8/label → 500 {"error": "Something went wrong on our side"}
GET /products/99/label → 404 {"error": "Product not found"}
The check then reads your log through /_test/log, a route it adds only for the test, and expects one line: GET /products/8/label failed: sql: Scan error on column index 1, name "category": converting NULL to string is unsupported.
Your task: rewrite the label handler so any error answers only {"error": "Something went wrong on our side"} with 500 and logs the method, the path and the error, while a missing product answers 404.
Assignment
Follow the Coding Tutorial to rewrite the label handler: scan the product, answer 404 for sql.ErrNoRows, log any other error and answer a safe message, then answer the label.
Hint
The sql.ErrNoRows check sits above if err != nil, since sql.ErrNoRows isn't nil either. Every answer inside an if needs a return below it, or the handler goes on and answers twice. Pass log.Printf the text "%s %s failed: %s", then r.Method, r.URL.Path and err.
Free plan: 30 tutor messages. PRO is unlimited.
