-
Notifications
You must be signed in to change notification settings - Fork 4
/
helpers.go
157 lines (133 loc) · 4.2 KB
/
helpers.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
package dimension
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"regexp"
errs "github.com/ONSdigital/dp-dataset-api/apierrors"
"github.com/ONSdigital/dp-dataset-api/models"
"github.com/ONSdigital/dp-dataset-api/mongo"
dprequest "github.com/ONSdigital/dp-net/v2/request"
"github.com/ONSdigital/log.go/v2/log"
"github.com/pkg/errors"
)
const (
reqUser = "req_user"
reqCaller = "req_caller"
)
// Regex const for patch paths
var (
optionNodeIDRegex = regexp.MustCompile("/([^/]+)/options/([^/]+)/node_id")
optionOrderRegex = regexp.MustCompile("/([^/]+)/options/([^/]+)/order")
slashRegex = regexp.MustCompile("/")
)
// isNodeIDPath checks if the provided string matches '/{dimension}/options/{option}/node_id' exactly once
func isNodeIDPath(p string) bool {
return len(optionNodeIDRegex.FindAllString(p, -1)) == 1
}
// isOrderPath checks if the provided string matches '/{dimension}/options/{option}/order' exactly once
func isOrderPath(p string) bool {
return len(optionOrderRegex.FindAllString(p, -1)) == 1
}
// createOptionFromPath creates a *DimensionOption struct pointer from the provided path, containing only Name and Option
// note that this method assumes that the path has already been validated (with isNodeIDPath or isOrderPath)
func createOptionFromPath(p string) *models.DimensionOption {
spl := slashRegex.Split(p, 5)
return &models.DimensionOption{
Name: spl[1], // {dimension} value from patch path
Option: spl[3], // {option} value from patch path
}
}
// getOptionsArrayFromInterface obtains an array of *CachedDimensionOption from the provided interface
func getOptionsArrayFromInterface(elements interface{}) ([]*models.CachedDimensionOption, error) {
options := []*models.CachedDimensionOption{}
// elements should be an array
arr, ok := elements.([]interface{})
if !ok {
return options, errors.New("missing list of items")
}
// each item in the array should be an option
for _, v := range arr {
// need to re-marshal, as it is currently a map
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
// unmarshal and validate CachedDimensionOption structure
option, err := unmarshalDimensionCache(bytes.NewBuffer(b))
if err != nil {
return nil, err
}
options = append(options, option)
}
return options, nil
}
func unmarshalDimensionCache(reader io.Reader) (*models.CachedDimensionOption, error) {
b, err := io.ReadAll(reader)
if err != nil {
return nil, errs.ErrUnableToReadMessage
}
var option models.CachedDimensionOption
err = json.Unmarshal(b, &option)
if err != nil {
return nil, errs.ErrUnableToParseJSON
}
if option.Name == "" || (option.Option == "" && option.CodeList == "") {
return nil, errs.ErrMissingParameters
}
return &option, nil
}
func handleDimensionErr(ctx context.Context, w http.ResponseWriter, err error, data log.Data) {
if data == nil {
data = log.Data{}
}
var status int
// Switch by error type
switch err.(type) {
case errs.ErrInvalidPatch:
status = http.StatusBadRequest
default:
// Switch by error message
switch {
case errs.NotFoundMap[err]:
status = http.StatusNotFound
case errs.BadRequestMap[err]:
status = http.StatusBadRequest
case errs.ConflictRequestMap[err]:
status = http.StatusConflict
default:
status = http.StatusInternalServerError
err = errors.WithMessage(err, "internal error")
}
}
data["response_status"] = status
logError(ctx, err, data)
http.Error(w, err.Error(), status)
}
func logError(ctx context.Context, err error, data log.Data) {
if user := dprequest.User(ctx); user != "" {
data[reqUser] = user
}
if caller := dprequest.Caller(ctx); caller != "" {
data[reqCaller] = caller
}
log.Error(ctx, "unsuccessful request", err, data)
}
func getIfMatch(r *http.Request) string {
ifMatch := r.Header.Get("If-Match")
if ifMatch == "" {
return mongo.AnyETag
}
return ifMatch
}
func setJSONPatchContentType(w http.ResponseWriter) {
w.Header().Add("Content-Type", "application/json-patch+json")
}
func writeBody(ctx context.Context, w http.ResponseWriter, b []byte, data log.Data) {
if _, err := w.Write(b); err != nil {
log.Error(ctx, "failed to write response body", err, data)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}