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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
module Annotation_severity = struct
type t =
| Error
| Warning
| Info
let variant_constructor_name = function
| Error -> "Error"
| Warning -> "Warning"
| Info -> "Info"
;;
let to_json t : Json.t = `String (variant_constructor_name t)
let of_string = function
| "Error" -> Some Error
| "Warning" -> Some Warning
| "Info" -> Some Info
| _ -> None
;;
end
module User_list = struct
type t = User_handle.t list
let of_json json : t =
match (json : Json.t) with
| `List xs -> List.map xs ~f:User_handle.of_json
| _ ->
raise (Json.Error (json, "User handle list expected to be a list of json strings."))
;;
end
type t =
{ default_repo_owner : User_handle.t option
; user_mentions_allowlist : User_handle.t list option
; invalid_crs_annotation_severity : Annotation_severity.t option
; crs_due_now_annotation_severity : Annotation_severity.t option
}
let to_json
{ default_repo_owner
; user_mentions_allowlist
; invalid_crs_annotation_severity
; crs_due_now_annotation_severity
}
: Json.t
=
let opt field to_json = function
| None -> []
| Some v -> [ field, to_json v ]
in
`Assoc
(List.concat
[ default_repo_owner |> opt "default_repo_owner" User_handle.to_json
; user_mentions_allowlist
|> opt "user_mentions_allowlist" (fun xs ->
`List (List.map xs ~f:User_handle.to_json))
; invalid_crs_annotation_severity
|> opt "invalid_crs_annotation_severity" Annotation_severity.to_json
; crs_due_now_annotation_severity
|> opt "crs_due_now_annotation_severity" Annotation_severity.to_json
])
;;
let get_json_enum_constructor json ~loc ~field_name =
match json with
| `String str -> `Unwrapped str
| `List [ `String str ] -> `Wrapped str
| _ ->
Err.raise
~loc
Pp.O.
[ Pp.text "In: " ++ Pp.text (Json.to_string json)
; Pp.text "Field "
++ Pp_tty.kwd (module String) field_name
++ Pp.text " expected to be a json string."
]
;;
let raise_duplicate_field ~loc field_name =
Err.raise
~loc
Pp.O.
[ Pp.text "Json object field "
++ Pp_tty.kwd (module String) field_name
++ Pp.text " is duplicated in this config."
]
;;
let set_field_once ~loc ~field_name ref_cell value =
match !ref_cell with
| Some _ -> raise_duplicate_field ~loc field_name
| None -> ref_cell := Some value
;;
let parse_severity_field ~loc ~emit_github_annotations ~field_name json =
let parse_string str =
match Annotation_severity.of_string str with
| Some t -> t
| None ->
Err.raise
~loc
Pp.O.
[ Pp.text "Field " ++ Pp_tty.kwd (module String) field_name ++ Pp.text ":"
; Pp.textf "Unsupported annotation severity %S." str
]
in
match get_json_enum_constructor json ~loc ~field_name with
| `Unwrapped str -> parse_string str
| `Wrapped str ->
let severity = parse_string str in
User_message.warning
~loc
~emit_github_annotations
Pp.O.
[ Pp.text "The config field name "
++ Pp_tty.kwd (module String) field_name
++ Pp.text " is now expected to be a json string rather than a list."
]
~hints:[ Pp.textf "Change it to simply: %S" str ];
severity
;;
let parse_json json ~loc ~emit_github_annotations =
match json with
| `Assoc fields ->
let default_repo_owner_ref = ref None in
let user_mentions_allowlist_ref = ref None in
let invalid_crs_annotation_severity_ref = ref None in
let crs_due_now_annotation_severity_ref = ref None in
List.iter fields ~f:(fun (field_name, value) ->
match field_name with
| "$schema" ->
()
| "default_repo_owner" ->
set_field_once ~loc ~field_name default_repo_owner_ref (User_handle.of_json value)
| "user_mentions_allowlist" ->
set_field_once
~loc
~field_name
user_mentions_allowlist_ref
(User_list.of_json value)
| "user_mentions_whitelist" ->
User_message.warning
~loc
~emit_github_annotations
Pp.O.
[ Pp.text "The config field name "
++ Pp_tty.kwd (module String) field_name
++ Pp.text " is deprecated and was renamed "
++ Pp_tty.kwd (module String) "user_mentions_allowlist"
++ Pp.text "."
]
~hints:[ Pp.text "Upgrade the config to use the new name." ];
set_field_once
~loc
~field_name:"user_mentions_allowlist"
user_mentions_allowlist_ref
(User_list.of_json value)
| "invalid_crs_annotation_severity" ->
set_field_once
~loc
~field_name
invalid_crs_annotation_severity_ref
(parse_severity_field ~loc ~emit_github_annotations ~field_name value)
| "crs_due_now_annotation_severity" ->
set_field_once
~loc
~field_name
crs_due_now_annotation_severity_ref
(parse_severity_field ~loc ~emit_github_annotations ~field_name value)
| _ ->
User_message.warning
~loc
~emit_github_annotations
[ Pp.textf "Unknown config field \"%s\"." field_name ]
~hints:[ Pp.text "Check the documentation for valid field names." ]);
{ default_repo_owner = !default_repo_owner_ref
; user_mentions_allowlist = !user_mentions_allowlist_ref
; invalid_crs_annotation_severity = !invalid_crs_annotation_severity_ref
; crs_due_now_annotation_severity = !crs_due_now_annotation_severity_ref
}
| _ -> Err.raise ~loc [ Pp.text "Config expected to be a json object." ]
;;
let default_repo_owner t = t.default_repo_owner
let user_mentions_allowlist t = t.user_mentions_allowlist
let invalid_crs_annotation_severity t = t.invalid_crs_annotation_severity
let crs_due_now_annotation_severity t = t.crs_due_now_annotation_severity
let create
?default_repo_owner
?user_mentions_allowlist
?invalid_crs_annotation_severity
?crs_due_now_annotation_severity
()
=
{ default_repo_owner
; user_mentions_allowlist
; invalid_crs_annotation_severity
; crs_due_now_annotation_severity
}
;;
let empty =
{ default_repo_owner = None
; user_mentions_allowlist = None
; invalid_crs_annotation_severity = None
; crs_due_now_annotation_severity = None
}
;;
let load_exn ~path ~emit_github_annotations =
match Yojson_five.Basic.from_file (Fpath.to_string path) with
| Ok json ->
let loc = Loc.of_file ~path in
(try parse_json json ~loc ~emit_github_annotations with
| Json.Error (json, msg) ->
Err.raise
~loc
Pp.O.
[ Pp.text "Invalid config."
; Pp.text "In: " ++ Pp.text (Json.to_string json)
; Pp.text msg
])
| Error msg ->
Err.raise ~loc:(Loc.of_file ~path) [ Pp.text "Not a valid json file."; Pp.text msg ]
;;