Browse Source

添加yaml_checker

yjp 1 year ago
parent
commit
2e4b8ded6e

+ 0 - 2
go.sum

@@ -1,5 +1,3 @@
-git.sxidc.com/go-tools/pipeline v0.0.0-20231107123717-990956b855f8 h1:FjhX1I8cjoaaW5jSaZbnQ7zilCLjwksVUoVVXkNnOr0=
-git.sxidc.com/go-tools/pipeline v0.0.0-20231107123717-990956b855f8/go.mod h1:0edkUfJ52WCL8dXlsn5gS968gx1469Ge2ccBmQy2yag=
 github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
 github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
 github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=

+ 7 - 0
yaml_checker/log.go

@@ -0,0 +1,7 @@
+package yaml_checker
+
+import "log"
+
+func init() {
+	log.SetPrefix("[YAML-CHECKER]")
+}

+ 55 - 0
yaml_checker/schema.yaml

@@ -0,0 +1,55 @@
+schema:
+  - type: field
+    name: test_field_array
+    describe: 测试field数组(必传)
+    required: true
+    as_array: true
+    field_type: string
+  - type: field
+    name: test_field
+    describe: 测试field(必传)
+    required: true
+    field_type: string
+  - type: object
+    name: test_obj_array
+    describe: 测试对象(必传)
+    required: true
+    as_array: true
+    fields:
+      - type: field
+        name: field
+        describe: 测试对象字段类型字段(非必传)
+        required: false
+        field_type: string
+  - type: object
+    name: test_obj
+    describe: 测试对象(必传)
+    required: true
+    fields:
+      - type: field
+        name: field_array
+        describe: 测试对象字段类型数组字段(必传)
+        required: true
+        as_array: true
+        field_type: string
+      - type: field
+        name: field
+        describe: 测试对象字段类型字段(非必传)
+        required: false
+        field_type: string
+      - type: object
+        name: obj_array
+        describe: 测试对象包含对象数组(必传)
+        required: true
+        as_array: true
+        fields:
+          - type: field
+            name: obj_array_field1
+            describe: 对象数组字段1(必传)
+            required: true
+            field_type: string
+          - type: field
+            name: obj_array_field2
+            describe: 对象数组字段2(非必传)
+            required: false
+            field_type: string

+ 184 - 0
yaml_checker/schema_doc.go

@@ -0,0 +1,184 @@
+package yaml_checker
+
+import (
+	"errors"
+	"gopkg.in/yaml.v3"
+	"os"
+)
+
+const (
+	schemaNodeTypeObject = "object"
+	schemaNodeTypeField  = "field"
+)
+
+type SchemaDoc struct {
+	rootSchemas []SchemaNode
+}
+
+type SchemaDocYaml struct {
+	RootSchemas []SchemaYaml `yaml:"schema"`
+}
+
+type SchemaYaml struct {
+	Type     string `yaml:"type"`
+	Name     string `yaml:"name"`
+	Describe string `yaml:"describe"`
+	Required bool   `yaml:"required"`
+	AsArray  bool   `yaml:"as_array"`
+
+	// object
+	Fields []SchemaYaml `yaml:"fields"`
+
+	// field
+	FieldType string `yaml:"field_type"`
+}
+
+func NewSchemaDoc(schemaFilePath string) (*SchemaDoc, error) {
+	if isStringEmpty(schemaFilePath) || !pathExists(schemaFilePath) {
+		return nil, errors.New("schema文件不存在")
+	}
+
+	validatorFileBytes, err := os.ReadFile(schemaFilePath)
+	if err != nil {
+		return nil, errors.New("读取schema文件错误: " + err.Error())
+	}
+
+	schemaDocYaml := new(SchemaDocYaml)
+	err = yaml.Unmarshal(validatorFileBytes, schemaDocYaml)
+	if err != nil {
+		return nil, errors.New("schema文件unmarshal错误: " + err.Error())
+	}
+
+	if schemaDocYaml.RootSchemas == nil || len(schemaDocYaml.RootSchemas) == 0 {
+		return nil, errors.New("schema文件结构错误: 没有根节点")
+	}
+
+	doc := new(SchemaDoc)
+	for _, rootSchema := range schemaDocYaml.RootSchemas {
+		rootSchemaNode, err := newSchemaNode(&rootSchema)
+		if err != nil {
+			return nil, err
+		}
+
+		doc.rootSchemas = append(doc.rootSchemas, rootSchemaNode)
+	}
+
+	return doc, nil
+}
+
+func newSchemaNode(schemaNodeYaml *SchemaYaml) (SchemaNode, error) {
+	switch schemaNodeYaml.Type {
+	case schemaNodeTypeObject:
+		return newObjectSchemaNode(schemaNodeYaml)
+	case schemaNodeTypeField:
+		return newFieldSchemaNode(schemaNodeYaml)
+	default:
+		return nil, errors.New("不支持的schema节点类型")
+	}
+}
+
+func newObjectSchemaNode(schemaNodeYaml *SchemaYaml) (*ObjectSchemaNode, error) {
+	if schemaNodeYaml.Type != schemaNodeTypeObject {
+		return nil, errors.New("schema节点类型错误: 期望类型" + schemaNodeTypeObject + " 实际类型" + schemaNodeYaml.Type)
+	}
+
+	if isStringEmpty(schemaNodeYaml.Name) {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有name属性")
+	}
+
+	if isStringEmpty(schemaNodeYaml.Describe) {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有describe属性")
+	}
+
+	if schemaNodeYaml.Fields == nil || len(schemaNodeYaml.Fields) == 0 {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有fields属性")
+	}
+
+	fieldSchemaNodes := make([]SchemaNode, 0)
+	for _, field := range schemaNodeYaml.Fields {
+		fieldSchemaNode, err := newSchemaNode(&field)
+		if err != nil {
+			return nil, err
+		}
+
+		fieldSchemaNodes = append(fieldSchemaNodes, fieldSchemaNode)
+	}
+
+	return &ObjectSchemaNode{
+		BaseNamingSchemaNode: BaseNamingSchemaNode{
+			Type:     schemaNodeYaml.Type,
+			Name:     schemaNodeYaml.Name,
+			Describe: schemaNodeYaml.Describe,
+			Required: schemaNodeYaml.Required,
+		},
+		AsArray: schemaNodeYaml.AsArray,
+		Fields:  fieldSchemaNodes,
+	}, nil
+}
+
+func newFieldSchemaNode(schemaNodeYaml *SchemaYaml) (*FieldSchemaNode, error) {
+	if schemaNodeYaml.Type != schemaNodeTypeField {
+		return nil, errors.New("schema节点类型错误: 期望类型" + schemaNodeTypeField + " 实际类型" + schemaNodeYaml.Type)
+	}
+
+	if isStringEmpty(schemaNodeYaml.Name) {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有name属性")
+	}
+
+	if isStringEmpty(schemaNodeYaml.Describe) {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有describe属性")
+	}
+
+	if isStringEmpty(schemaNodeYaml.FieldType) {
+		return nil, errors.New(schemaNodeTypeObject + "schema类型必须有field_type属性")
+	}
+
+	return &FieldSchemaNode{
+		BaseNamingSchemaNode: BaseNamingSchemaNode{
+			Type:     schemaNodeYaml.Type,
+			Name:     schemaNodeYaml.Name,
+			Describe: schemaNodeYaml.Describe,
+			Required: schemaNodeYaml.Required,
+		},
+		AsArray:   schemaNodeYaml.AsArray,
+		FieldType: schemaNodeYaml.FieldType,
+	}, nil
+}
+
+func (schemaDoc *SchemaDoc) ValidateFile(filePath string) error {
+	if !pathExists(filePath) {
+		return errors.New("文件不存在: " + filePath)
+	}
+
+	fileBytes, err := os.ReadFile(filePath)
+	if err != nil {
+		return errors.New("读取文件错误: " + err.Error())
+	}
+
+	yamlMap := make(map[string]any)
+	err = yaml.Unmarshal(fileBytes, yamlMap)
+	if err != nil {
+		return errors.New("unmarshal文件错误: " + err.Error())
+	}
+
+	return schemaDoc.validate(yamlMap)
+}
+
+func (schemaDoc *SchemaDoc) ValidateMap(yamlMap map[string]any) error {
+	if yamlMap == nil || len(yamlMap) == 0 {
+		return errors.New("没有yaml的map")
+	}
+
+	return schemaDoc.validate(yamlMap)
+}
+
+func (schemaDoc *SchemaDoc) validate(yamlMap map[string]any) error {
+	for _, rootSchemaNode := range schemaDoc.rootSchemas {
+		err := rootSchemaNode.Validate(yamlMap)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 135 - 0
yaml_checker/schema_node.go

@@ -0,0 +1,135 @@
+package yaml_checker
+
+import (
+	"errors"
+	"reflect"
+)
+
+type SchemaNode interface {
+	Validate(yamlMap map[string]any) error
+}
+
+type BaseNamingSchemaNode struct {
+	Type     string
+	Name     string
+	Describe string
+	Required bool
+}
+
+type ObjectSchemaNode struct {
+	BaseNamingSchemaNode
+	AsArray bool
+	Fields  []SchemaNode
+}
+
+func (schema *ObjectSchemaNode) Validate(yamlMap map[string]any) error {
+	yamlValue, ok := yamlMap[schema.Name]
+	if !ok {
+		if schema.Required {
+			return errors.New(schemaNodeTypeObject + "类型的yaml节点必须有" + schema.Name)
+		}
+
+		return nil
+	}
+
+	if yamlValue == nil {
+		return errors.New(schemaNodeTypeObject + "类型的yaml节点的" + schema.Name + "字段没有赋值")
+	}
+
+	if schema.AsArray {
+		sliceParamValues, ok := yamlValue.([]any)
+		if !ok {
+			return errors.New(schemaNodeTypeObject + "类型节点" + schema.Name + "的as_array为true,但是值不是数组")
+		}
+
+		for _, sliceParamValue := range sliceParamValues {
+			sliceParamMapValue, ok := sliceParamValue.(map[string]any)
+			if !ok {
+				return errors.New(schemaNodeTypeObject + "类型节点" + schema.Name + "的值类型转换失败")
+			}
+
+			err := schema.validateValue(sliceParamMapValue)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	yamlMapValue, ok := yamlValue.(map[string]any)
+	if !ok {
+		return errors.New(schemaNodeTypeObject + "类型节点不是键值对")
+	}
+
+	err := schema.validateValue(yamlMapValue)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (schema *ObjectSchemaNode) validateValue(yamlMap map[string]any) error {
+	for _, field := range schema.Fields {
+		err := field.Validate(yamlMap)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+type FieldSchemaNode struct {
+	BaseNamingSchemaNode
+	AsArray   bool
+	FieldType string
+}
+
+func (schema *FieldSchemaNode) Validate(yamlMap map[string]any) error {
+	yamlValue, ok := yamlMap[schema.Name]
+	if !ok {
+		if schema.Required {
+			return errors.New(schemaNodeTypeField + "类型的yaml节点必须有" + schema.Name)
+		}
+
+		return nil
+	}
+
+	if yamlValue == nil {
+		return errors.New(schemaNodeTypeField + "类型的yaml节点的" + schema.Name + "字段没有赋值")
+	}
+
+	if schema.AsArray {
+		sliceParamValues, ok := yamlValue.([]any)
+		if !ok {
+			return errors.New(schemaNodeTypeField + "类型节点as_array为true,但是值不是数组")
+		}
+
+		for _, sliceParamValue := range sliceParamValues {
+			err := schema.validateValue(sliceParamValue)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	err := schema.validateValue(yamlValue)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (schema *FieldSchemaNode) validateValue(paramValue any) error {
+	valueType := reflect.TypeOf(paramValue).String()
+	if valueType != schema.FieldType {
+		return errors.New(schemaNodeTypeField + "类型节点的" + schema.Name + "字段期望类型为: " + schema.FieldType + " 实际类型为: " + valueType)
+	}
+
+	return nil
+}

+ 16 - 0
yaml_checker/test.yaml

@@ -0,0 +1,16 @@
+test_field_array:
+  - aaa
+  - bbb
+  - ccc
+test_field: ddd
+test_obj_array:
+  - field: aaa
+test_obj:
+  field_array:
+    - aaa
+    - bbb
+  field: ccc
+  obj_array:
+    - obj_array_field1: aaa
+    - obj_array_field1: bbb
+      obj_array_field2: ccc

+ 27 - 0
yaml_checker/utils.go

@@ -0,0 +1,27 @@
+package yaml_checker
+
+import (
+	"os"
+	"strings"
+)
+
+func isStringEmpty(s string) bool {
+	return strings.Trim(s, " ") == ""
+}
+
+func isStringNotEmpty(s string) bool {
+	return strings.Trim(s, " ") != ""
+}
+
+func pathExists(path string) bool {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true
+	}
+
+	if os.IsNotExist(err) {
+		return false
+	}
+
+	return false
+}

+ 15 - 0
yaml_checker/yaml_checker_test.go

@@ -0,0 +1,15 @@
+package yaml_checker
+
+import "testing"
+
+func Test(t *testing.T) {
+	schemaDoc, err := NewSchemaDoc("schema.yaml")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = schemaDoc.ValidateFile("test.yaml")
+	if err != nil {
+		t.Fatal(err)
+	}
+}