horizoncd/horizon

View on GitHub
pkg/util/jsonschema/jsonschema.go

Summary

Maintainability
A
40 mins
Test Coverage
C
70%
// Copyright © 2023 Horizoncd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 
package jsonschema
 
import (
"encoding/json"
"fmt"
 
herrors "github.com/horizoncd/horizon/core/errors"
perror "github.com/horizoncd/horizon/pkg/errors"
v5jsonschema "github.com/santhosh-tekuri/jsonschema/v5"
)
 
var (
unevaluatedProperties = "unevaluatedProperties"
properties = "properties"
)
 
// Validate json by jsonschema.
// schema and document support 2 types: string, map[string]interface{}
Function `Validate` has 6 return statements (exceeds 4 allowed).
func Validate(schema, document interface{}, setUnevaluatedPropertiesToFalse bool) error {
// change schema type to Golang map
var schemaMap map[string]interface{}
switch schema := schema.(type) {
case string:
err := json.Unmarshal([]byte(schema), &schemaMap)
if err != nil {
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("json unmarshal error, schema: %s, error: %s", schema, err.Error()))
}
case map[string]interface{}:
schemaMap = schema
default:
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("unsported type: %T for schema", schema))
}
 
// add "unevaluatedProperties": false
if setUnevaluatedPropertiesToFalse {
addUnevaluatedPropertiesField(schemaMap)
}
 
var v interface{}
switch document := document.(type) {
case string:
if err := json.Unmarshal([]byte(document), &v); err != nil {
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("json unmarshal error, document: %s, error: %s", document, err.Error()))
}
case map[string]interface{}:
v = document
default:
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("unsported type: %T for document", document))
}
 
schemaStr, err := json.Marshal(schemaMap)
if err != nil {
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("json marshal error, document: %s, error: %s", document, err.Error()))
}
sch, err := v5jsonschema.CompileString("schema.json", string(schemaStr))
if err != nil {
return perror.Wrap(herrors.ErrParamInvalid,
fmt.Sprintf("jsonschema compilestring error, schema: %s, error: %s", schemaStr, err.Error()))
}
if err = sch.Validate(v); err != nil {
return perror.Wrap(herrors.ErrParamInvalid, err.Error())
}
 
return nil
}
 
// addUnevaluatedPropertiesField add "unevaluatedProperties": false to the jsonschema
// which means no additional properties will be allowed.
func addUnevaluatedPropertiesField(m map[string]interface{}) map[string]interface{} {
_, propertiesExist := m[properties]
_, unevaluatedPropertiesExist := m[unevaluatedProperties]
// ignore when schema has already set unevaluatedProperties field
if propertiesExist && !unevaluatedPropertiesExist {
m[unevaluatedProperties] = false
}
 
for _, v := range m {
v1, ok := v.(map[string]interface{})
if ok {
addUnevaluatedPropertiesField(v1)
}
}
 
return m
}