github.com/hashicorp/hcl/v2@v2.20.0/json/public.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package json
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  )
    13  
    14  // Parse attempts to parse the given buffer as JSON and, if successful, returns
    15  // a hcl.File for the HCL configuration represented by it.
    16  //
    17  // This is not a generic JSON parser. Instead, it deals only with the profile
    18  // of JSON used to express HCL configuration.
    19  //
    20  // The returned file is valid only if the returned diagnostics returns false
    21  // from its HasErrors method. If HasErrors returns true, the file represents
    22  // the subset of data that was able to be parsed, which may be none.
    23  func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
    24  	return ParseWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
    25  }
    26  
    27  // ParseWithStartPos attempts to parse like json.Parse, but unlike json.Parse
    28  // you can pass a start position of the given JSON as a hcl.Pos.
    29  //
    30  // In most cases json.Parse should be sufficient, but it can be useful for parsing
    31  // a part of JSON with correct positions.
    32  func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
    33  	rootNode, diags := parseFileContent(src, filename, start)
    34  
    35  	switch rootNode.(type) {
    36  	case *objectVal, *arrayVal:
    37  		// okay
    38  	default:
    39  		diags = diags.Append(&hcl.Diagnostic{
    40  			Severity: hcl.DiagError,
    41  			Summary:  "Root value must be object",
    42  			Detail:   "The root value in a JSON-based configuration must be either a JSON object or a JSON array of objects.",
    43  			Subject:  rootNode.StartRange().Ptr(),
    44  		})
    45  
    46  		// Since we've already produced an error message for this being
    47  		// invalid, we'll return an empty placeholder here so that trying to
    48  		// extract content from our root body won't produce a redundant
    49  		// error saying the same thing again in more general terms.
    50  		fakePos := hcl.Pos{
    51  			Byte:   0,
    52  			Line:   1,
    53  			Column: 1,
    54  		}
    55  		fakeRange := hcl.Range{
    56  			Filename: filename,
    57  			Start:    fakePos,
    58  			End:      fakePos,
    59  		}
    60  		rootNode = &objectVal{
    61  			Attrs:     []*objectAttr{},
    62  			SrcRange:  fakeRange,
    63  			OpenRange: fakeRange,
    64  		}
    65  	}
    66  
    67  	file := &hcl.File{
    68  		Body: &body{
    69  			val: rootNode,
    70  		},
    71  		Bytes: src,
    72  		Nav:   navigation{rootNode},
    73  	}
    74  	return file, diags
    75  }
    76  
    77  // ParseExpression parses the given buffer as a standalone JSON expression,
    78  // returning it as an instance of Expression.
    79  func ParseExpression(src []byte, filename string) (hcl.Expression, hcl.Diagnostics) {
    80  	return ParseExpressionWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
    81  }
    82  
    83  // ParseExpressionWithStartPos parses like json.ParseExpression, but unlike
    84  // json.ParseExpression you can pass a start position of the given JSON
    85  // expression as a hcl.Pos.
    86  func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) {
    87  	node, diags := parseExpression(src, filename, start)
    88  	return &expression{src: node}, diags
    89  }
    90  
    91  // ParseFile is a convenience wrapper around Parse that first attempts to load
    92  // data from the given filename, passing the result to Parse if successful.
    93  //
    94  // If the file cannot be read, an error diagnostic with nil context is returned.
    95  func ParseFile(filename string) (*hcl.File, hcl.Diagnostics) {
    96  	f, err := os.Open(filename)
    97  	if err != nil {
    98  		return nil, hcl.Diagnostics{
    99  			{
   100  				Severity: hcl.DiagError,
   101  				Summary:  "Failed to open file",
   102  				Detail:   fmt.Sprintf("The file %q could not be opened.", filename),
   103  			},
   104  		}
   105  	}
   106  	defer f.Close()
   107  
   108  	src, err := ioutil.ReadAll(f)
   109  	if err != nil {
   110  		return nil, hcl.Diagnostics{
   111  			{
   112  				Severity: hcl.DiagError,
   113  				Summary:  "Failed to read file",
   114  				Detail:   fmt.Sprintf("The file %q was opened, but an error occured while reading it.", filename),
   115  			},
   116  		}
   117  	}
   118  
   119  	return Parse(src, filename)
   120  }