func Parse(data []byte) (types.Config, report.Report) { var cfg types.Config var r report.Report if err := yaml.Unmarshal(data, &cfg); err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError) } nodes := yaml.UnmarshalToNode(data) if nodes == nil { r.Add(report.Entry{ Kind: report.EntryWarning, Message: "Configuration is empty", }) r.Merge(validate.ValidateWithoutSource(reflect.ValueOf(cfg))) } else { root, err := fromYamlDocumentNode(*nodes) if err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError) } r.Merge(validate.Validate(reflect.ValueOf(cfg), root, nil)) } if r.IsFatal() { return types.Config{}, r } return cfg, r }
func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) { if isEmpty(rawConfig) { return types.Config{}, report.Report{}, ErrEmpty } else if isCloudConfig(rawConfig) { return types.Config{}, report.Report{}, ErrCloudConfig } else if isScript(rawConfig) { return types.Config{}, report.Report{}, ErrScript } var err error var config types.Config // These errors are fatal and the config should not be further validated if err = json.Unmarshal(rawConfig, &config); err == nil { versionReport := config.Ignition.Version.Validate() if versionReport.IsFatal() { return types.Config{}, versionReport, ErrInvalid } } // Handle json syntax and type errors first, since they are fatal but have offset info if serr, ok := err.(*json.SyntaxError); ok { line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset) return types.Config{}, report.Report{ Entries: []report.Entry{{ Kind: report.EntryError, Message: serr.Error(), Line: line, Column: col, Highlight: highlight, }}, }, ErrInvalid } if terr, ok := err.(*json.UnmarshalTypeError); ok { line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset) return types.Config{}, report.Report{ Entries: []report.Entry{{ Kind: report.EntryError, Message: terr.Error(), Line: line, Column: col, Highlight: highlight, }}, }, ErrInvalid } // Handle other fatal errors (i.e. invalid version) if err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError), err } // Unmarshal again to a json.Node to get offset information for building a report var ast json.Node var r report.Report configValue := reflect.ValueOf(config) if err := json.Unmarshal(rawConfig, &ast); err != nil { r.Add(report.Entry{ Kind: report.EntryWarning, Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.", }) r.Merge(validate.ValidateWithoutSource(configValue)) } else { r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig))) } if r.IsFatal() { return types.Config{}, r, ErrInvalid } return config, r, nil }
func validateStruct(vObj reflect.Value, ast AstNode, source io.ReadSeeker) report.Report { r := report.Report{} // isFromObject will be true if this struct was unmarshalled from a JSON object. keys, isFromObject := map[string]AstNode{}, false if ast != nil { keys, isFromObject = ast.KeyValueMap() } // Maintain a set of key's that have been used. usedKeys := map[string]struct{}{} // Maintain a list of all the tags in the struct for fuzzy matching later. tags := []string{} for _, f := range getFields(vObj) { // Default to nil AstNode if the field's corrosponding node cannot be found. var sub_node AstNode // Default to passing a nil source if the field's corrosponding node cannot be found. // This ensures the line numbers reported from all sub-structs are 0 and will be changed by AddPosition var src io.ReadSeeker // Try to determine the json.Node that corrosponds with the struct field if isFromObject { tag := strings.SplitN(f.Type.Tag.Get(ast.Tag()), ",", 2)[0] // Save the tag so we have a list of all the tags in the struct tags = append(tags, tag) // mark that this key was used usedKeys[tag] = struct{}{} if sub, ok := keys[tag]; ok { // Found it sub_node = sub src = source } } sub_report := Validate(f.Value, sub_node, src) // Default to deepest node if the node's type isn't an object, // such as when a json string actually unmarshal to structs (like with version) line, col := 0, 0 if ast != nil { line, col, _ = ast.ValueLineCol(src) } sub_report.AddPosition(line, col, "") r.Merge(sub_report) } if !isFromObject { // If this struct was not unmarshalled from a JSON object, there cannot be unused keys. return r } for k, v := range keys { if _, hasKey := usedKeys[k]; hasKey { continue } line, col, highlight := v.KeyLineCol(source) typo := similar(k, tags) r.Add(report.Entry{ Kind: report.EntryWarning, Message: fmt.Sprintf("Config has unrecognized key: %s", k), Line: line, Column: col, Highlight: highlight, }) if typo != "" { r.Add(report.Entry{ Kind: report.EntryInfo, Message: fmt.Sprintf("Did you mean %s instead of %s", typo, k), Line: line, Column: col, Highlight: highlight, }) } } return r }