fix(iot): 409 различает конфликт name vs device_id (ревью)

store.Create классифицирует 23505 по имени constraint: (namespace,device_id) →
ErrDeviceIDExists, иначе ErrAlreadyExists. Handler отдаёт разные 409-сообщения.
Помогает Terraform понять, какое поле конфликтует.
This commit is contained in:
Repinoid
2026-10-10 19:21:47 +03:00
parent 3b7c8a0a63
commit 3a73207abd
3 changed files with 18 additions and 0 deletions
+4
View File
@@ -215,6 +215,10 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) {
} }
if err := h.Devices.Create(r.Context(), device); err != nil { if err := h.Devices.Create(r.Context(), device); err != nil {
if errors.Is(err, store.ErrDeviceIDExists) {
writeJSON(w, http.StatusConflict, errResp("device_id already exists in this namespace"))
return
}
if errors.Is(err, store.ErrAlreadyExists) { if errors.Is(err, store.ErrAlreadyExists) {
writeJSON(w, http.StatusConflict, errResp("iot device already exists")) writeJSON(w, http.StatusConflict, errResp("iot device already exists"))
return return
+4
View File
@@ -15,6 +15,10 @@ var ErrNotFound = errors.New("device not found")
// ErrAlreadyExists — конфликт уникальности (namespace, name) или (namespace, device_id). // ErrAlreadyExists — конфликт уникальности (namespace, name) или (namespace, device_id).
var ErrAlreadyExists = errors.New("device already exists") var ErrAlreadyExists = errors.New("device already exists")
// ErrDeviceIDExists — конфликт именно по (namespace, device_id) (отличается от
// конфликта по name для понятной ошибки API/Terraform, ревью 2026-10-10).
var ErrDeviceIDExists = errors.New("device_id already exists")
// Device — устройство IoT из таблицы iot_devices. // Device — устройство IoT из таблицы iot_devices.
type Device struct { type Device struct {
Namespace string Namespace string
+10
View File
@@ -6,6 +6,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/lib/pq"
) )
// Create вставляет новое устройство. Конфликт (namespace, name) или // Create вставляет новое устройство. Конфликт (namespace, name) или
@@ -26,6 +29,13 @@ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
) )
if err != nil { if err != nil {
if isUniqueViolation(err) { if isUniqueViolation(err) {
// Различаем конфликт name и device_id по имени constraint (ревью
// 2026-10-10): PK — iot_devices_pkey (namespace,name), уникальный
// индекс — idx_iot_devices_ns_device (namespace,device_id).
var pqErr *pq.Error
if errors.As(err, &pqErr) && strings.Contains(pqErr.Constraint, "ns_device") {
return ErrDeviceIDExists
}
return ErrAlreadyExists return ErrAlreadyExists
} }
return fmt.Errorf("store: insert device: %w", err) return fmt.Errorf("store: insert device: %w", err)