Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add json string validator #164

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions stringvalidator/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package stringvalidator

import (
"context"
"encoding/json"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

var _ validator.String = IsJsonValidator{}

// IsJsonValidator validates that a string is valid Json.
type IsJsonValidator struct{}

// Description describes the validation in plain text formatting.
func (validator IsJsonValidator) Description(_ context.Context) string {
return "string must be valid json"
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator IsJsonValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func NormalizeJsonString(jsonString interface{}) (string, error) {
var j interface{}

if jsonString == nil || jsonString.(string) == "" {
return "", nil
}

s := jsonString.(string)

err := json.Unmarshal([]byte(s), &j)
if err != nil {
return s, err
}

bytes, _ := json.Marshal(j)
return string(bytes[:]), nil
}

// Validate performs the validation.
func (v IsJsonValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) {
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
return
}

value := request.ConfigValue.ValueString()

if _, err := NormalizeJsonString(value); err != nil {
response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic(
request.Path,
v.Description(ctx),
fmt.Sprintf("%s", v),
))

return
}
}

// IsJson returns an validator which validates string value is valid json
func IsJson() validator.String {
return IsJsonValidator{}
}
79 changes: 79 additions & 0 deletions stringvalidator/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package stringvalidator_test

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
)

func TestIsJsonValidator(t *testing.T) {
t.Parallel()

type testCase struct {
val types.String
expectError bool
}
tests := map[string]testCase{
"unknown": {
val: types.StringUnknown(),
},
"null": {
val: types.StringNull(),
},
"empty": {
val: types.StringValue(``),
},
"empty brackets": {
val: types.StringValue(`{}`),
},
"valid json": {
val: types.StringValue(`{"abc":["1","2"]}`),
},
"Invalid 1": {
val: types.StringValue(`{0:"1"}`),
expectError: true,
},
"Invalid 2": {
val: types.StringValue(`{'abc':1}`),
expectError: true,
},
"Invalid 3": {
val: types.StringValue(`{"def":}`),
expectError: true,
},
"Invalid 4": {
val: types.StringValue(`{"xyz":[}}`),
expectError: true,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()
request := validator.StringRequest{
Path: path.Root("test"),
PathExpression: path.MatchRoot("test"),
ConfigValue: test.val,
}
response := validator.StringResponse{}
stringvalidator.IsJson().ValidateString(context.TODO(), request, &response)

if !response.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}
})
}
}