· 6 years ago · Apr 07, 2020, 03:44 PM
1// Package handler implements different Handlers for processing ParsedOutput.
2package handler
3
4import (
5 "bufio"
6 "fmt"
7 types2 "git.internal.gracenote.com/Music-Works/logger/types"
8 po "git.internal.gracenote.com/Music-Works/parser/internal/parsedoutput"
9 "go/types"
10 "strings"
11)
12
13const defaultKeyName = "key"
14
15type jsonHandler struct {
16 writer *bufio.Writer
17 currentLevel po.Level
18 inProgress bool
19 keyName string
20 lg types2.Logger
21
22 es errorState
23 parentInputText map[po.Level]string
24}
25
26// Error state is used to allow the parser to continue even after error, by keeping track of the error records state
27// and level.
28type errorState struct {
29 state bool
30 level po.Level
31}
32
33// Parser Error specifies errors coming from the Parser API
34type ParseError struct {
35 Err error
36}
37
38func (r *ParseError) Error() string {
39 return r.Err.Error()
40}
41
42// NewJSONHandler creates a jsonHandler which satisfies the Handler interface.
43func NewJSONHandler(writer *bufio.Writer, lg types2.Logger, keyName string) (Handler, error) {
44 lg.CreateChildWithFields(map[string]interface{}{"writer": writer}).Trace("NewJSONHandler called")
45 h := &jsonHandler{
46 writer: writer,
47 lg: lg,
48 keyName: getKeyName(keyName),
49 parentInputText: make(map[po.Level]string),
50 }
51 err := h.startDocument()
52 return h, err
53}
54
55// Gets the keyName, default value is "key"
56func getKeyName(keyName string) string {
57 if keyName == "" {
58 return defaultKeyName
59 }
60 return keyName
61}
62
63// OnProcessed processes the ParsedOutput then writes to and flushes its Writer.
64// This method also does mandatory field checks and performs actions based on the error state of the handler.
65func (h *jsonHandler) OnProcessed(p po.ParsedOutput) error {
66 h.lg.CreateChildWithFields(map[string]interface{}{"p": p}).Trace("OnProcessed called")
67
68 // Keep track of the InputText
69 h.parentInputText[p.Level] = p.InputText
70
71 // Check for nil mandatory fields, add error to parsedOutput
72 for _, field := range p.Fields {
73 if field.IsMandatory && field.ValueType == types.UntypedNil {
74 err := fmt.Errorf("mandatory field \"%s\" is nil", field.Key)
75 p.Fields = []po.Field{{Key: "error", Value: err.Error(), ValueType: types.String}}
76 return h.onError(p)
77 }
78 }
79
80 // When valid record is found, return to normal state.
81 if h.es.state && isSiblingOrParentOfErrRecord(h.es.level, p.Level) {
82 h.es.state = false
83 }
84
85 // Skip parsing of record when in error state.
86 if h.es.state {
87 h.lg.CreateChildWithFields(map[string]interface{}{"input": p.InputText, "parent record": getParent(h.es.level, h.parentInputText)}).Trace("Skip parsing of %s record due to parent record error", p.RecordKey)
88 return nil
89 }
90
91 return h.onProcessed(p)
92}
93
94// OnError adds error Field and calls onProcessed to write to its Writer.
95// This method performs actions based on the error state of the handler.
96func (h *jsonHandler) OnError(p po.ParsedOutput) error {
97 h.lg.Trace("OnError called")
98
99 // Keep track of the InputText
100 h.parentInputText[p.Level] = p.InputText
101
102 // TODO: Check for mandatory error field
103
104 // TODO: Check for mandatory nil field
105
106 // TODO: Log Warn optional errors
107
108
109 return h.onError(p)
110}
111
112// onError is an internal method to the OnError Handler method.
113func (h *jsonHandler) onError(p po.ParsedOutput) error {
114 if err := p.FindFirstErrorInFields(); err != nil {
115 // Skip parsing of record if already in an error state from an invalid parent record.
116 if h.es.state && isChildOfErrRecord(h.es.level, p.Level) {
117 h.lg.CreateChildWithFields(map[string]interface{}{"input": p.InputText, "parent record": getParent(h.es.level, h.parentInputText)}).Error("%s", p.FindFirstErrorInFields())
118 return &ParseError{Err: err}
119 }
120
121 // Update errorState and error record level
122 h.es.state = true
123 h.es.level = p.Level
124 h.lg.CreateChildWithFields(map[string]interface{}{"input": p.InputText, "parent record": getParent(h.es.level, h.parentInputText)}).Error("%s", p.FindFirstErrorInFields())
125
126 // Clear fields and just add Error Field
127 p.Fields = []po.Field{{Key: "error", Value: err.Error(), ValueType: types.String}}
128
129 // Write error record to file and return any error. If no errors, return ParseError.
130 err = h.onProcessed(p)
131 if err != nil {
132 return err
133 }
134
135 return &ParseError{Err: err}
136 }
137
138 return h.onProcessed(p)
139}
140
141// getParentLevel returns the raw input of the parent of the current record using the parentLevelInput map
142func getParent(lvl po.Level, ps map[po.Level]string) string {
143 if val, ok := ps[lvl-1]; ok {
144 return val
145 }
146 return ""
147}
148
149func isSiblingOrParentOfErrRecord(errLvl, currLvl po.Level) bool {
150 return errLvl >= currLvl
151}
152
153func isChildOfErrRecord(errLvl, currLvl po.Level) bool {
154 return errLvl < currLvl
155}
156
157// OnComplete finishes any remaining unclosed elements and flushes its Writer.
158func (h *jsonHandler) OnComplete() error {
159 h.lg.Trace("OnComplete called")
160 err := h.endDocument()
161 if err != nil {
162 return err
163 }
164
165 err = h.writer.Flush()
166 return err
167}
168
169// onProcessed is a private method that processes the ParsedOutput then writes to and flushes its Writer.
170func (h *jsonHandler) onProcessed(p po.ParsedOutput) error {
171 // Discard ParsedOutput with no fields
172 if isAllNilFields(p) {
173 return nil
174 }
175
176 var err error
177 switch {
178 case !h.inProgress:
179 err = h.processFirst(p)
180 case p.Level == h.currentLevel:
181 err = h.processSibling(p)
182 case p.Level == h.currentLevel+1:
183 err = h.processChild(p)
184 case p.Level < h.currentLevel:
185 err = h.processAncestor(p)
186 default:
187 err = fmt.Errorf("invalid level transition from Level%d to Level%d", h.currentLevel, p.Level)
188 }
189
190 if err != nil {
191 h.lg.Error("encountered error OnProcess: %s", err.Error())
192 }
193
194 h.currentLevel = p.Level // Save level context for next operation
195 if flushErr := h.writer.Flush(); flushErr != nil {
196 h.lg.Error("jsonHandler could not flush output after OnProcess: %s", flushErr)
197 }
198 return err
199}
200
201// processFirst is called on the first ParsedOutput of the process
202func (h *jsonHandler) processFirst(p po.ParsedOutput) error {
203 if p.Level != po.Level0 {
204 return fmt.Errorf("first ParsedOutput must be Level0, was given Level%d", p.Level)
205 }
206 h.inProgress = true // Setting inProgress signifies that an element has started and is unclosed
207 return h.startElement(p)
208}
209
210// processSibling is called when a ParsedOutput is of the same Level as the last
211func (h *jsonHandler) processSibling(p po.ParsedOutput) error {
212 err := h.endElement(true)
213 if err != nil {
214 return err
215 }
216 return h.startElement(p)
217}
218
219// processChild is called when a ParsedOutput is exactly one level lower than the last
220func (h *jsonHandler) processChild(p po.ParsedOutput) error {
221 err := h.startChildren(p)
222 if err != nil {
223 return err
224 }
225 err = h.startElement(p)
226 return err
227}
228
229// processAncestor is called when a ParsedOutput is higher than the last
230func (h *jsonHandler) processAncestor(p po.ParsedOutput) error {
231 err := h.endChildren(p.Level)
232 if err != nil {
233 return err
234 }
235 err = h.endElement(true)
236 if err != nil {
237 return err
238 }
239 err = h.startElement(p)
240 return err
241}
242
243// startDocument is called when the Handler is initialized
244func (h *jsonHandler) startDocument() error {
245 el := "["
246 _, err := h.writer.WriteString(el)
247 return err
248}
249
250// endDocument is called when the Handler is completed
251func (h *jsonHandler) endDocument() error {
252 err := h.endChildren(po.Level0 - 1) // End "above" top-level element
253 return err
254}
255
256// startElement processes a ParsedOutput, writing all fields to the output
257func (h *jsonHandler) startElement(p po.ParsedOutput) error {
258 el := "{"
259 el += "\"" + h.keyName + "\":\"" + p.RecordKey + "\""
260 for _, field := range p.Fields {
261 // Add comma to preceding field since another attribute is following
262 el += ",\"" + field.Key + "\":" + getFieldValueForType(field.Value, field.ValueType)
263 }
264 _, err := h.writer.WriteString(el)
265 return err
266}
267
268// endElement finalizes a single output element, closing any remaining braces
269func (h *jsonHandler) endElement(addComma bool) error {
270 el := "}"
271 if addComma {
272 el += ","
273 }
274 _, err := h.writer.WriteString(el)
275 return err
276}
277
278// startChildren begins an array field in the current element
279func (h *jsonHandler) startChildren(p po.ParsedOutput) error {
280 el := ",\"Records\":["
281 _, err := h.writer.WriteString(el)
282 return err
283}
284
285// endChildren ends the array fields and elements up to the specified level
286func (h *jsonHandler) endChildren(upToLevel po.Level) error {
287 for i := upToLevel; i < h.currentLevel; i++ {
288 if h.inProgress {
289 err := h.endElement(false)
290 if err != nil {
291 return err
292 }
293 }
294 el := "]"
295 _, err := h.writer.WriteString(el)
296 if err != nil {
297 return err
298 }
299 }
300 return nil
301}
302
303// getFieldValueForType specifies how field values of different types should be written
304func getFieldValueForType(v string, vt types.BasicKind) string {
305 if vt == types.Float32 || vt == types.Float64 || vt == types.Int {
306 return v // Do not wrap numbers in quote marks
307 }
308 return fmt.Sprintf("\"%s\"", escapeString(v)) // Default case: wrap value in quote marks
309}
310
311// escapeString escapes special characters that are reserved in JSON.
312func escapeString(s string) string {
313 return jsonEscaper.Replace(s)
314}
315
316var jsonEscaper = strings.NewReplacer(
317 `"`, `\"`, // Escape double quotes
318 `\`, `\\`, // Escape backslash
319 `/`, `\/`, // Escape solidus
320 "\b", `\b`, // Escape backspace
321 "\f", `\f`, // Escape form feed
322 "\n", `\n`, // Escape newline
323 "\r", `\r`, // Escape carriage return
324 "\t", `\t`, // Escape horizontal tab
325)
326
327// isAllNilFields checks if a ParsedOutput contains only nil fields
328func isAllNilFields(p po.ParsedOutput) bool {
329 hasNil := true
330 for _, d := range p.Fields {
331 hasNil = d.ValueType == types.UntypedNil
332 if !hasNil {
333 break
334 }
335 }
336 return hasNil
337}