// SPDX-License-Identifier: MIT package checker import ( "encoding/json" "sort" "strings" ) // ParseGrokOutput decodes the JSON produced by `dnsviz grok` into a typed // map of zone analyses. The returned Order slice lists zone FQDNs sorted // from the most-specific (queried name) up to the root, matching DNSViz's // natural chain order. func ParseGrokOutput(raw []byte) (map[string]ZoneAnalysis, []string, error) { var top map[string]json.RawMessage if err := json.Unmarshal(raw, &top); err != nil { return nil, nil, err } out := make(map[string]ZoneAnalysis, len(top)) for k, v := range top { out[k] = decodeZone(v) } keys := make([]string, 0, len(out)) for k := range out { keys = append(keys, k) } sort.Slice(keys, func(i, j int) bool { return labelDepth(keys[i]) > labelDepth(keys[j]) }) return out, keys, nil } func decodeZone(raw json.RawMessage) ZoneAnalysis { var z ZoneAnalysis var m map[string]json.RawMessage if err := json.Unmarshal(raw, &m); err != nil { var s string if json.Unmarshal(raw, &s) == nil { z.Status = s } return z } if v, ok := m["status"]; ok { _ = json.Unmarshal(v, &z.Status) } z.Errors = decodeFindings(m["errors"]) z.Warnings = decodeFindings(m["warnings"]) delete(m, "status") delete(m, "errors") delete(m, "warnings") if len(m) > 0 { z.Extra = make(map[string]any, len(m)) for k, v := range m { var any any _ = json.Unmarshal(v, &any) z.Extra[k] = any } } return z } func decodeFindings(raw json.RawMessage) []Finding { if len(raw) == 0 { return nil } var arr []map[string]any if err := json.Unmarshal(raw, &arr); err != nil { var strs []string if json.Unmarshal(raw, &strs) == nil { out := make([]Finding, 0, len(strs)) for _, s := range strs { out = append(out, Finding{Description: s}) } return out } return nil } out := make([]Finding, 0, len(arr)) for _, item := range arr { f := Finding{} if s, ok := item["code"].(string); ok { f.Code = s } if s, ok := item["description"].(string); ok && s != "" { f.Description = s } else if s, ok := item["message"].(string); ok && s != "" { f.Description = s } else { b, _ := json.Marshal(item) f.Description = string(b) } if servers, ok := item["servers"].([]any); ok { for _, srv := range servers { if s, ok := srv.(string); ok { f.Servers = append(f.Servers, s) } } } out = append(out, f) } return out } func labelDepth(zone string) int { z := strings.TrimSuffix(zone, ".") if z == "" { return 0 } return strings.Count(z, ".") + 1 }