appmanager: refactor schema into interface, introduce cuelang
diff --git a/core/installer/schema.go b/core/installer/schema.go
new file mode 100644
index 0000000..abafa15
--- /dev/null
+++ b/core/installer/schema.go
@@ -0,0 +1,130 @@
+package installer
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/cue/cuecontext"
+)
+
+type Kind int
+
+const (
+ KindBoolean Kind = 0
+ KindString = 1
+ KindStruct = 2
+ KindNetwork = 3
+)
+
+type Schema interface {
+ Kind() Kind
+ Fields() map[string]Schema
+}
+
+const networkSchema = `
+#Network: {
+ IngressClass: string
+ CertificateIssuer: string
+ Domain: string
+}
+
+value: %s
+
+valid: #Network & value
+`
+
+func isNetwork(v cue.Value) bool {
+ if v.Value().Kind() != cue.StructKind {
+ return false
+ }
+ value := fmt.Sprintf("%#v", v)
+ s := fmt.Sprintf(networkSchema, value)
+ c := cuecontext.New()
+ u := c.CompileString(s)
+ return u.Err() == nil && u.Validate() == nil
+}
+
+type basicSchema struct {
+ kind Kind
+}
+
+func (s basicSchema) Kind() Kind {
+ return s.kind
+}
+
+func (s basicSchema) Fields() map[string]Schema {
+ return nil
+}
+
+type structSchema struct {
+ fields map[string]Schema
+}
+
+func (s structSchema) Kind() Kind {
+ return KindStruct
+}
+
+func (s structSchema) Fields() map[string]Schema {
+ return s.fields
+}
+
+func NewCueSchema(v cue.Value) (Schema, error) {
+ switch v.Value().Kind() {
+ case cue.StringKind:
+ return basicSchema{KindString}, nil
+ case cue.StructKind:
+ if isNetwork(v) {
+ return basicSchema{KindNetwork}, nil
+ }
+ s := structSchema{make(map[string]Schema)}
+ f, err := v.Fields()
+ if err != nil {
+ return nil, err
+ }
+ for f.Next() {
+ scm, err := NewCueSchema(f.Value())
+ if err != nil {
+ return nil, err
+ }
+ s.fields[f.Selector().String()] = scm
+ }
+ return s, nil
+ default:
+ return nil, fmt.Errorf("SHOULD NOT REACH!")
+ }
+}
+
+func newSchema(schema map[string]any) (Schema, error) {
+ switch schema["type"] {
+ case "string":
+ if r, ok := schema["role"]; ok && r == "network" {
+ return basicSchema{KindNetwork}, nil
+ } else {
+ return basicSchema{KindString}, nil
+ }
+ case "object":
+ s := structSchema{make(map[string]Schema)}
+ props := schema["properties"].(map[string]any)
+ for name, schema := range props {
+ sm, _ := schema.(map[string]any)
+ scm, err := newSchema(sm)
+ if err != nil {
+ return nil, err
+ }
+ s.fields[name] = scm
+ }
+ return s, nil
+ default:
+ return nil, fmt.Errorf("SHOULD NOT REACH!")
+ }
+}
+
+func NewJSONSchema(schema string) (Schema, error) {
+ ret := make(map[string]any)
+ if err := json.NewDecoder(strings.NewReader(schema)).Decode(&ret); err != nil {
+ return nil, err
+ }
+ return newSchema(ret)
+}