-
Notifications
You must be signed in to change notification settings - Fork 38
/
issues.go
175 lines (156 loc) · 5.23 KB
/
issues.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package hstspreload
import (
"encoding/json"
"fmt"
)
// An IssueCode is a string identifier for an Issue.
// This allows other programs to perform analysis or take actions
// based on specific issues.
//
// Examples: "domain.is_subdomain", "domain.tls.cannot_connect", "header.preloadable.max_age.below_1_year"
type IssueCode string
// An Issue is an error or a warning relating to a site's HSTS preload
// configuration.
type Issue struct {
// An error code.
Code IssueCode `json:"code"`
// A short summary (≈2-5 words) of the issue.
Summary string `json:"summary"`
// A detailed explanation with instructions for fixing.
Message string `json:"message"`
}
// The Issues struct encapsulates a set of errors and warnings.
// By convention:
//
// - Errors contains a list of errors that will prevent preloading.
//
// - Warnings contains a list errors that are a good idea to fix,
// but are okay for preloading.
//
// - Warning and errors will state at which level the issue occurred (e.g. header syntax, preload requirement checking, HTTP response checking, domain checking).
//
// - If Issues is returned from a Check____() function without any errors
// or warnings, it means that the function passed all checks.
//
// - The list of errors is not guaranteed to be exhaustive. In
// particular, fixing a given error (e.g. "could not connect to
// server") may bring another error to light (e.g. "HSTS header was
// not found").
type Issues struct {
Errors []Issue `json:"errors"`
Warnings []Issue `json:"warnings"`
}
func (iss Issues) addErrorf(code IssueCode, summary string, format string, args ...interface{}) Issues {
formattedError := fmt.Sprintf(format, args...)
return Issues{
Errors: append(iss.Errors, Issue{code, summary, formattedError}),
Warnings: iss.Warnings,
}
}
func (iss Issues) addWarningf(code IssueCode, summary string, format string, args ...interface{}) Issues {
formattedWarning := fmt.Sprintf(format, args...)
return Issues{
Errors: iss.Errors,
Warnings: append(iss.Warnings, Issue{code, summary, formattedWarning}),
}
}
func (iss Issues) addUniqueErrorf(code IssueCode, summary string, format string, args ...interface{}) Issues {
for _, err := range iss.Errors {
if err.Code == code {
return iss
}
}
return iss.addErrorf(code, summary, format, args...)
}
func (iss Issues) addUniqueWarningf(code IssueCode, summary string, format string, args ...interface{}) Issues {
for _, warning := range iss.Warnings {
if warning.Code == code {
return iss
}
}
return iss.addWarningf(code, summary, format, args...)
}
func combineIssues(issues1 Issues, issues2 Issues) Issues {
return Issues{
Errors: append(issues1.Errors, issues2.Errors...),
Warnings: append(issues1.Warnings, issues2.Warnings...),
}
}
// Match checks that the given issues match the `wanted` ones. This
// function always checks that both the lists of Errors and Warnings
// have the same number of `Issue`s with the same `IssuesCode`s codes in
// the same order. If any issues in `wanted` have the Summary or Message
// field set, the field is also compared against the field from the
// corresponding issue in `iss`.
func (iss Issues) Match(wanted Issues) bool {
if len(iss.Errors) != len(wanted.Errors) {
return false
}
if len(iss.Warnings) != len(wanted.Warnings) {
return false
}
for e := range iss.Errors {
if iss.Errors[e].Code != wanted.Errors[e].Code {
return false
}
if wanted.Errors[e].Summary != "" && iss.Errors[e].Summary != wanted.Errors[e].Summary {
return false
}
if wanted.Errors[e].Message != "" && iss.Errors[e].Message != wanted.Errors[e].Message {
return false
}
}
for w := range iss.Warnings {
if iss.Warnings[w].Code != wanted.Warnings[w].Code {
return false
}
if wanted.Warnings[w].Summary != "" && iss.Warnings[w].Summary != wanted.Warnings[w].Summary {
return false
}
if wanted.Warnings[w].Message != "" && iss.Warnings[w].Message != wanted.Warnings[w].Message {
return false
}
}
return true
}
func formatIssueListForString(list []Issue) string {
output := ""
if len(list) > 1 {
for _, l := range list {
output += fmt.Sprintf("\n %#v,", l)
}
output += "\n "
} else if len(list) == 1 {
output = fmt.Sprintf(`%#v`, list[0])
}
return output
}
// GoString formats `iss` with multiple lines and indentation.
// This is mainly used to provide output for unit tests in this project
// that can be pasted back into the relevant unit tess.
func (iss Issues) GoString() string {
return fmt.Sprintf(`Issues{
Errors: []string{%s},
Warnings: []string{%s},
}`,
formatIssueListForString(iss.Errors),
formatIssueListForString(iss.Warnings),
)
}
// MarshalJSON converts the given Issues to JSON, making sure that
// empty Errors/Warnings are converted to empty lists rather than null.
func (iss Issues) MarshalJSON() ([]byte, error) {
// We explicitly fill out the fields with slices so that they are
// marshalled to `[]` rather than `null` when they are empty.
if len(iss.Errors) == 0 {
iss.Errors = make([]Issue, 0)
}
if len(iss.Warnings) == 0 {
iss.Warnings = make([]Issue, 0)
}
// We use a type alias to call the "default" implementation of
// json.Marshal on Issues.
// See http://choly.ca/post/go-json-marshalling/
type issuesData Issues
return json.Marshal(issuesData(iss))
}