Repository: kcl-lang/kcl-go
Pull request #132 (2023-08-09 07:16:37)

feat: support more validation rules when generating from json schema
<!-- Thank you for contributing to KCL!

Note: 

1. With pull requests:

    - Open your pull request against "main"
    - Your pull request should have no more than two commits, if not you should squash them.
    - It should pass all tests in the available continuous integration systems such as GitHub Actions.
    - You should add/modify tests to cover your proposed code changes.
    - If your pull request contains a new feature, please document it on the README.

2. Please create an issue first to describe the problem.

    We recommend that link the issue with the PR in the following question.
    For more info, check https://kcl-lang.io/docs/community/contribute/
-->

#### 1. Does this PR affect any open issues?(Y/N) and add issue references 

- [ ] N
- [x] Y 

part of https://github.com/kcl-lang/kcl/issues/526
<!-- You can add issue references here. 
    e.g. 
    fix #123, re #123, 
    fix https://github.com/XXX/issues/44
-->

#### 2. What is the scope of this PR (e.g. component or file name):

<!-- You can add the scope of this change here. 
    e.g. 
    /src/server/core.rs,
    kcl-lang/kcl/src/parser 
-->

#### 3. Provide a description of the PR(e.g. more details, effects, motivations or doc link):

<!-- You can choose a brief description here -->
- [ ] Affects user behaviors
- [ ] Contains syntax changes
- [ ] Contains variable changes
- [ ] Contains experimental features
- [ ] Performance regression: Consumes more CPU
- [ ] Performance regression: Consumes more Memory
- [x] Other

Support convert `pattern`, `multipleOf`, `uniqueItems`, `minItems` and `maxItems` keywords from json schema to kcl schema.
<!-- You can add more details here.
    e.g. 
    Call method "XXXX" to ..... in order to ....,
    More details: https://XXXX.com/doc......
-->

#### 4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link):

- [x] N
- [ ] Y 

<!-- You can add more details here.
    e.g. 
    Calling method "XXXX" will cause the "XXXX", "XXXX" modules to be affected.
    More details: https://XXXX.com/doc......
-->

#### 5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:

<!-- You can choose a brief description here -->
- [x] Unit test
- [ ] Integration test
- [ ] Benchmark (add benchmark stats below)
- [ ] Manual test (add detailed scripts or steps below)
- [ ] Other

<!-- You can add more details here.
e.g. 
The test case in XXXX is used to .....
test cases in /src/tests/XXXXX
test cases https://github.com/XXX/pull/44
benchmark stats: time XXX ms
-->



Patch:
diff --git a/pkg/tools/gen/genkcl_jsonschema.go b/pkg/tools/gen/genkcl_jsonschema.go
index 362d011..8a06ab0 100644
--- a/pkg/tools/gen/genkcl_jsonschema.go
+++ b/pkg/tools/gen/genkcl_jsonschema.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"io"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"github.com/iancoleman/strcase"
@@ -38,7 +39,10 @@ func (k *kclGenerator) genSchemaFromJsonSchema(w io.Writer, filename string, src
 	js.Validate(context.Background(), nil)
 
 	// convert json schema to kcl schema
-	ctx := convertContext{resultMap: make(map[string]convertResult)}
+	ctx := convertContext{
+		resultMap: make(map[string]convertResult),
+		imports:   make(map[string]struct{}),
+	}
 	result := convertSchemaFromJsonSchema(ctx, js,
 		strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)))
 	if !result.IsSchema {
@@ -215,6 +219,34 @@ func convertSchemaFromJsonSchema(ctx convertContext, s *jsonschema.Schema, name
 			result.Validations = append(result.Validations, validation{
 				MaxLength: (*int)(v),
 			})
+		case *jsonschema.Pattern:
+			result.Validations = append(result.Validations, validation{
+				Regex: (*regexp.Regexp)(v),
+			})
+			ctx.imports["regex"] = struct{}{}
+		case *jsonschema.MultipleOf:
+			vInt := int(*v)
+			if float64(vInt) != float64(*v) {
+				logger.GetLogger().Warningf("unsupported multipleOf value: %f", *v)
+				continue
+			}
+			result.Validations = append(result.Validations, validation{
+				MultiplyOf: &vInt,
+			})
+		case *jsonschema.UniqueItems:
+			if *v {
+				result.Validations = append(result.Validations, validation{
+					Unique: true,
+				})
+			}
+		case *jsonschema.MinItems:
+			result.Validations = append(result.Validations, validation{
+				MinLength: (*int)(v),
+			})
+		case *jsonschema.MaxItems:
+			result.Validations = append(result.Validations, validation{
+				MaxLength: (*int)(v),
+			})
 		default:
 			logger.GetLogger().Warningf("unknown Keyword: %s", k)
 		}
diff --git a/pkg/tools/gen/templates/validator.gotmpl b/pkg/tools/gen/templates/validator.gotmpl
index 7108244..1355fe8 100644
--- a/pkg/tools/gen/templates/validator.gotmpl
+++ b/pkg/tools/gen/templates/validator.gotmpl
@@ -11,4 +11,13 @@
     {{- if .MinLength }}
         len({{ formatName .Name }}) >= {{ .MinLength }}
     {{- end }}
+    {{- if .Regex }}
+        regex.match({{ formatName .Name }}, r"{{ .Regex }}")
+    {{- end }}
+    {{- if .MultiplyOf }}
+        multiplyof({{ formatName .Name }}, {{ .MultiplyOf }})
+    {{- end }}
+    {{- if .Unique }}
+        isunique({{ formatName .Name }})
+    {{- end }}
 {{- end -}}
diff --git a/pkg/tools/gen/types.go b/pkg/tools/gen/types.go
index 537bad1..1693098 100644
--- a/pkg/tools/gen/types.go
+++ b/pkg/tools/gen/types.go
@@ -3,6 +3,7 @@ package gen
 import (
 	"bytes"
 	"fmt"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -154,6 +155,9 @@ type validation struct {
 	ExclusiveMaximum bool
 	MinLength        *int
 	MaxLength        *int
+	Regex            *regexp.Regexp
+	MultiplyOf       *int
+	Unique           bool
 }
 
 // indexSignature is a kcl schema index signature definition.


Test patch:
diff --git a/pkg/tools/gen/testdata/jsonschema/items/expect.k b/pkg/tools/gen/testdata/jsonschema/items/expect.k
new file mode 100644
index 0000000..2d500e5
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/items/expect.k
@@ -0,0 +1,22 @@
+"""
+This file was generated by the KCL auto-gen tool. DO NOT EDIT.
+Editing this file might prove futile when you re-run the KCL auto-gen generate command.
+"""
+
+schema Book:
+    """
+    Book
+
+    Attributes
+    ----------
+    title : str, optional
+    authors : [str], optional
+    """
+
+    title?: str
+    authors?: [str]
+
+    check:
+        len(authors) <= 5
+        len(authors) >= 1
+        isunique(authors)
diff --git a/pkg/tools/gen/testdata/jsonschema/items/input.json b/pkg/tools/gen/testdata/jsonschema/items/input.json
new file mode 100644
index 0000000..7484a00
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/items/input.json
@@ -0,0 +1,19 @@
+{
+	"$schema": "http://json-schema.org/draft-07/schema#",
+	"$id": "https://example.com/schemas/book.json",
+	"type": "object",
+	"properties": {
+		"title": {
+			"type": "string"
+		},
+		"authors": {
+			"type": "array",
+			"items": {
+				"type": "string"
+			},
+			"minItems": 1,
+			"maxItems": 5,
+			"uniqueItems": true
+		}
+	}
+}
diff --git a/pkg/tools/gen/testdata/jsonschema/multipleof/expect.k b/pkg/tools/gen/testdata/jsonschema/multipleof/expect.k
new file mode 100644
index 0000000..6b83929
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/multipleof/expect.k
@@ -0,0 +1,20 @@
+"""
+This file was generated by the KCL auto-gen tool. DO NOT EDIT.
+Editing this file might prove futile when you re-run the KCL auto-gen generate command.
+"""
+
+schema Book:
+    """
+    Book
+
+    Attributes
+    ----------
+    title : str, optional
+    price : int, optional
+    """
+
+    title?: str
+    price?: int
+
+    check:
+        multiplyof(price, 10)
diff --git a/pkg/tools/gen/testdata/jsonschema/multipleof/input.json b/pkg/tools/gen/testdata/jsonschema/multipleof/input.json
new file mode 100644
index 0000000..e7f5b5a
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/multipleof/input.json
@@ -0,0 +1,14 @@
+{
+	"$schema": "http://json-schema.org/draft-07/schema#",
+	"$id": "https://example.com/schemas/book.json",
+	"type": "object",
+	"properties": {
+		"title": {
+			"type": "string"
+		},
+		"price": {
+			"type": "integer",
+			"multipleOf": 10
+		}
+	}
+}
diff --git a/pkg/tools/gen/testdata/jsonschema/pattern/expect.k b/pkg/tools/gen/testdata/jsonschema/pattern/expect.k
new file mode 100644
index 0000000..bbff438
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/pattern/expect.k
@@ -0,0 +1,23 @@
+"""
+This file was generated by the KCL auto-gen tool. DO NOT EDIT.
+Editing this file might prove futile when you re-run the KCL auto-gen generate command.
+"""
+import regex
+
+schema Cronjob:
+    """
+    Cronjob
+
+    Attributes
+    ----------
+    name : str, optional
+    schedule : str, optional, default is "5 0 * * *"
+    command : str, optional
+    """
+
+    name?: str
+    schedule?: str = "5 0 * * *"
+    command?: str
+
+    check:
+        regex.match(schedule, r"^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$")
diff --git a/pkg/tools/gen/testdata/jsonschema/pattern/input.json b/pkg/tools/gen/testdata/jsonschema/pattern/input.json
new file mode 100644
index 0000000..b19007f
--- /dev/null
+++ b/pkg/tools/gen/testdata/jsonschema/pattern/input.json
@@ -0,0 +1,18 @@
+{
+	"$schema": "http://json-schema.org/draft-07/schema#",
+	"$id": "https://example.com/schemas/cronjob.json",
+	"type": "object",
+	"properties": {
+		"name": {
+			"type": "string"
+		},
+		"schedule": {
+			"type": "string",
+			"default": "5 0 * * *",
+			"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
+		},
+		"command": {
+			"type": "string"
+		}
+	}
+}