Skip to content

Commit

Permalink
decoder: Decouple each validator into its own type
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Aug 8, 2023
1 parent 01ef96c commit ec39696
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 237 deletions.
34 changes: 34 additions & 0 deletions decoder/internal/validator/attribute_deprecated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type DeprecatedAttribute struct{}

func (v DeprecatedAttribute) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
attr, ok := node.(*hclsyntax.Attribute)
if !ok {
return
}

if nodeSchema == nil {
return
}
attrSchema := nodeSchema.(*schema.AttributeSchema)
if attrSchema.IsDeprecated {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("%q is deprecated", attr.Name),
Detail: fmt.Sprintf("Reason: %q", attrSchema.Description.Value),
Subject: attr.SrcRange.Ptr(),
})
}

return
}
44 changes: 44 additions & 0 deletions decoder/internal/validator/attribute_missing_required.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type MissingRequiredAttribute struct{}

func (v MissingRequiredAttribute) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
body, ok := node.(*hclsyntax.Body)
if !ok {
return
}

if nodeSchema == nil {
return
}

bodySchema := nodeSchema.(*schema.BodySchema)
if bodySchema.Attributes == nil {
return
}

for name, attr := range bodySchema.Attributes {
if attr.IsRequired {
_, ok := body.Attributes[name]
if !ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Required attribute %q not specified", name),
Detail: fmt.Sprintf("An attribute named %q is required here", name),
Subject: body.SrcRange.Ptr(),
})
}
}
}

return
}
30 changes: 30 additions & 0 deletions decoder/internal/validator/attribute_unexpected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type UnexpectedAttribute struct{}

func (v UnexpectedAttribute) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
attr, ok := node.(*hclsyntax.Attribute)
if !ok {
return
}

if nodeSchema == nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unexpected attribute",
Detail: fmt.Sprintf("An attribute named %q is not expected here", attr.Name),
Subject: attr.SrcRange.Ptr(),
})
}

return
}
34 changes: 34 additions & 0 deletions decoder/internal/validator/block_deprecated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type DeprecatedBlock struct{}

func (v DeprecatedBlock) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
block, ok := node.(*hclsyntax.Block)
if !ok {
return
}

if nodeSchema == nil {
return
}
blockSchema := nodeSchema.(*schema.BlockSchema)
if blockSchema.IsDeprecated {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("%q is deprecated", block.Type),
Detail: fmt.Sprintf("Reason: %q", blockSchema.Description.Value),
Subject: block.TypeRange.Ptr(),
})
}

return
}
48 changes: 48 additions & 0 deletions decoder/internal/validator/block_labels_length.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type BlockLabelsLength struct{}

func (v BlockLabelsLength) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
block, ok := node.(*hclsyntax.Block)
if !ok {
return
}

if nodeSchema == nil {
return
}

blockSchema := nodeSchema.(*schema.BlockSchema)

validLabelNum := len(blockSchema.Labels)
for i := range block.Labels {
if i >= validLabelNum {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Too many labels specified for %q", block.Type),
Detail: fmt.Sprintf("Only %d label(s) are expected for %q blocks", validLabelNum, block.Type),
Subject: block.LabelRanges[i].Ptr(),
})
}
}

if validLabelNum > len(block.Labels) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Not enough labels specified for %q", block.Type),
Detail: fmt.Sprintf("All %q blocks must have %d label(s)", block.Type, validLabelNum),
Subject: block.TypeRange.Ptr(),
})
}

return
}
49 changes: 49 additions & 0 deletions decoder/internal/validator/block_max_items.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type MaxBlocks struct{}

func (v MaxBlocks) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
body, ok := node.(*hclsyntax.Body)
if !ok {
return
}

if nodeSchema == nil {
return
}

foundBlocks := make(map[string]uint64)
for _, block := range body.Blocks {
if _, ok := foundBlocks[block.Type]; !ok {
foundBlocks[block.Type] = 0
}

foundBlocks[block.Type]++
}

bodySchema := nodeSchema.(*schema.BodySchema)
for name, blockSchema := range bodySchema.Blocks {
if blockSchema.MaxItems != 0 {
foundBlocks, ok := foundBlocks[name]
if ok && foundBlocks > blockSchema.MaxItems {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Too many blocks specified for %q", name),
Detail: fmt.Sprintf("Only %d block(s) are expected for %q", blockSchema.MaxItems, name),
Subject: node.Range().Ptr(),
})
}
}
}

return
}
49 changes: 49 additions & 0 deletions decoder/internal/validator/block_min_items.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type MinBlocks struct{}

func (v MinBlocks) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
body, ok := node.(*hclsyntax.Body)
if !ok {
return
}

if nodeSchema == nil {
return
}

foundBlocks := make(map[string]uint64)
for _, block := range body.Blocks {
if _, ok := foundBlocks[block.Type]; !ok {
foundBlocks[block.Type] = 0
}

foundBlocks[block.Type]++
}

bodySchema := nodeSchema.(*schema.BodySchema)
for name, blockSchema := range bodySchema.Blocks {
if blockSchema.MinItems != 0 {
foundBlocks, ok := foundBlocks[name]
if !ok || foundBlocks < blockSchema.MinItems {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Too few blocks specified for %q", name),
Detail: fmt.Sprintf("At least %d block(s) are expected for %q", blockSchema.MinItems, name),
Subject: node.Range().Ptr(),
})
}
}
}

return
}
29 changes: 29 additions & 0 deletions decoder/internal/validator/block_unexpected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package validator

import (
"context"
"fmt"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type UnexpectedBlock struct{}

func (v UnexpectedBlock) Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) (diags hcl.Diagnostics) {
block, ok := node.(*hclsyntax.Block)
if !ok {
return
}

if nodeSchema == nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unexpected block",
Detail: fmt.Sprintf("Blocks of type %q are not expected here", block.Type),
Subject: block.TypeRange.Ptr(),
})
}
return
}
16 changes: 16 additions & 0 deletions decoder/internal/validator/validators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validator

import (
"context"

"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type Validator interface {
Visit(ctx context.Context, node hclsyntax.Node, nodeSchema schema.Schema) hcl.Diagnostics
}
Loading

0 comments on commit ec39696

Please sign in to comment.