package json import ( "fmt" "io/ioutil" "os" "github.com/hashicorp/hcl/v2" ) // Parse attempts to parse the given buffer as JSON and, if successful, returns // a hcl.File for the HCL configuration represented by it. // // This is not a generic JSON parser. Instead, it deals only with the profile // of JSON used to express HCL configuration. // // The returned file is valid only if the returned diagnostics returns false // from its HasErrors method. If HasErrors returns true, the file represents // the subset of data that was able to be parsed, which may be none. func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) { return ParseWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) } // ParseWithStartPos attempts to parse like json.Parse, but unlike json.Parse // you can pass a start position of the given JSON as a hcl.Pos. // // In most cases json.Parse should be sufficient, but it can be useful for parsing // a part of JSON with correct positions. func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) { rootNode, diags := parseFileContent(src, filename, start) switch rootNode.(type) { case *objectVal, *arrayVal: // okay default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Root value must be object", Detail: "The root value in a JSON-based configuration must be either a JSON object or a JSON array of objects.", Subject: rootNode.StartRange().Ptr(), }) // Since we've already produced an error message for this being // invalid, we'll return an empty placeholder here so that trying to // extract content from our root body won't produce a redundant // error saying the same thing again in more general terms. fakePos := hcl.Pos{ Byte: 0, Line: 1, Column: 1, } fakeRange := hcl.Range{ Filename: filename, Start: fakePos, End: fakePos, } rootNode = &objectVal{ Attrs: []*objectAttr{}, SrcRange: fakeRange, OpenRange: fakeRange, } } file := &hcl.File{ Body: &body{ val: rootNode, }, Bytes: src, Nav: navigation{rootNode}, } return file, diags } // ParseExpression parses the given buffer as a standalone JSON expression, // returning it as an instance of Expression. func ParseExpression(src []byte, filename string) (hcl.Expression, hcl.Diagnostics) { return ParseExpressionWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) } // ParseExpressionWithStartPos parses like json.ParseExpression, but unlike // json.ParseExpression you can pass a start position of the given JSON // expression as a hcl.Pos. func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) { node, diags := parseExpression(src, filename, start) return &expression{src: node}, diags } // ParseFile is a convenience wrapper around Parse that first attempts to load // data from the given filename, passing the result to Parse if successful. // // If the file cannot be read, an error diagnostic with nil context is returned. func ParseFile(filename string) (*hcl.File, hcl.Diagnostics) { f, err := os.Open(filename) if err != nil { return nil, hcl.Diagnostics{ { Severity: hcl.DiagError, Summary: "Failed to open file", Detail: fmt.Sprintf("The file %q could not be opened.", filename), }, } } defer f.Close() src, err := ioutil.ReadAll(f) if err != nil { return nil, hcl.Diagnostics{ { Severity: hcl.DiagError, Summary: "Failed to read file", Detail: fmt.Sprintf("The file %q was opened, but an error occured while reading it.", filename), }, } } return Parse(src, filename) }