github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/tfdiags/hcl.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package tfdiags 5 6 import ( 7 "github.com/hashicorp/hcl/v2" 8 ) 9 10 // hclDiagnostic is a Diagnostic implementation that wraps a HCL Diagnostic 11 type hclDiagnostic struct { 12 diag *hcl.Diagnostic 13 } 14 15 var _ Diagnostic = hclDiagnostic{} 16 17 func (d hclDiagnostic) Severity() Severity { 18 switch d.diag.Severity { 19 case hcl.DiagWarning: 20 return Warning 21 default: 22 return Error 23 } 24 } 25 26 func (d hclDiagnostic) Description() Description { 27 return Description{ 28 Summary: d.diag.Summary, 29 Detail: d.diag.Detail, 30 } 31 } 32 33 func (d hclDiagnostic) Source() Source { 34 var ret Source 35 if d.diag.Subject != nil { 36 rng := SourceRangeFromHCL(*d.diag.Subject) 37 ret.Subject = &rng 38 } 39 if d.diag.Context != nil { 40 rng := SourceRangeFromHCL(*d.diag.Context) 41 ret.Context = &rng 42 } 43 return ret 44 } 45 46 func (d hclDiagnostic) FromExpr() *FromExpr { 47 if d.diag.Expression == nil || d.diag.EvalContext == nil { 48 return nil 49 } 50 return &FromExpr{ 51 Expression: d.diag.Expression, 52 EvalContext: d.diag.EvalContext, 53 } 54 } 55 56 func (d hclDiagnostic) ExtraInfo() interface{} { 57 return d.diag.Extra 58 } 59 60 // SourceRangeFromHCL constructs a SourceRange from the corresponding range 61 // type within the HCL package. 62 func SourceRangeFromHCL(hclRange hcl.Range) SourceRange { 63 return SourceRange{ 64 Filename: hclRange.Filename, 65 Start: SourcePos{ 66 Line: hclRange.Start.Line, 67 Column: hclRange.Start.Column, 68 Byte: hclRange.Start.Byte, 69 }, 70 End: SourcePos{ 71 Line: hclRange.End.Line, 72 Column: hclRange.End.Column, 73 Byte: hclRange.End.Byte, 74 }, 75 } 76 } 77 78 // ToHCL constructs a HCL Range from the receiving SourceRange. This is the 79 // opposite of SourceRangeFromHCL. 80 func (r SourceRange) ToHCL() hcl.Range { 81 return hcl.Range{ 82 Filename: r.Filename, 83 Start: hcl.Pos{ 84 Line: r.Start.Line, 85 Column: r.Start.Column, 86 Byte: r.Start.Byte, 87 }, 88 End: hcl.Pos{ 89 Line: r.End.Line, 90 Column: r.End.Column, 91 Byte: r.End.Byte, 92 }, 93 } 94 } 95 96 // ToHCL constructs a hcl.Diagnostics containing the same diagnostic messages 97 // as the receiving tfdiags.Diagnostics. 98 // 99 // This conversion preserves the data that HCL diagnostics are able to 100 // preserve but would be lossy in a round trip from tfdiags to HCL and then 101 // back to tfdiags, because it will lose the specific type information of 102 // the source diagnostics. In most cases this will not be a significant 103 // problem, but could produce an awkward result in some special cases such 104 // as converting the result of ConsolidateWarnings, which will force the 105 // resulting warning groups to be flattened early. 106 func (diags Diagnostics) ToHCL() hcl.Diagnostics { 107 if len(diags) == 0 { 108 return nil 109 } 110 ret := make(hcl.Diagnostics, len(diags)) 111 for i, diag := range diags { 112 severity := diag.Severity() 113 desc := diag.Description() 114 source := diag.Source() 115 fromExpr := diag.FromExpr() 116 117 hclDiag := &hcl.Diagnostic{ 118 Summary: desc.Summary, 119 Detail: desc.Detail, 120 Severity: severity.ToHCL(), 121 } 122 if source.Subject != nil { 123 hclDiag.Subject = source.Subject.ToHCL().Ptr() 124 } 125 if source.Context != nil { 126 hclDiag.Context = source.Context.ToHCL().Ptr() 127 } 128 if fromExpr != nil { 129 hclDiag.Expression = fromExpr.Expression 130 hclDiag.EvalContext = fromExpr.EvalContext 131 } 132 133 ret[i] = hclDiag 134 } 135 return ret 136 }