Moved to CUE validation

This commit is contained in:
Martino Ferrari
2026-01-23 11:16:06 +01:00
parent 5c3f05a1a4
commit 5853365707
15 changed files with 511 additions and 477 deletions

View File

@@ -2,137 +2,73 @@ package schema
import (
_ "embed"
"encoding/json"
"fmt"
"os"
"path/filepath"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
//go:embed marte.json
var defaultSchemaJSON []byte
//go:embed marte.cue
var defaultSchemaCUE []byte
type Schema struct {
Classes map[string]ClassDefinition `json:"classes"`
}
type ClassDefinition struct {
Fields []FieldDefinition `json:"fields"`
Ordered bool `json:"ordered"`
Direction string `json:"direction"`
}
type FieldDefinition struct {
Name string `json:"name"`
Type string `json:"type"` // "int", "float", "string", "bool", "reference", "array", "node", "any"
Mandatory bool `json:"mandatory"`
Context *cue.Context
Value cue.Value
}
func NewSchema() *Schema {
ctx := cuecontext.New()
return &Schema{
Classes: make(map[string]ClassDefinition),
Context: ctx,
Value: ctx.CompileBytes(defaultSchemaCUE),
}
}
func LoadSchema(path string) (*Schema, error) {
// LoadSchema loads a CUE schema from a file and returns the cue.Value
func LoadSchema(ctx *cue.Context, path string) (cue.Value, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var s Schema
if err := json.Unmarshal(content, &s); err != nil {
return nil, fmt.Errorf("failed to parse schema: %v", err)
}
return &s, nil
}
// DefaultSchema returns the built-in embedded schema
func DefaultSchema() *Schema {
var s Schema
if err := json.Unmarshal(defaultSchemaJSON, &s); err != nil {
panic(fmt.Sprintf("failed to parse default embedded schema: %v", err))
}
if s.Classes == nil {
s.Classes = make(map[string]ClassDefinition)
}
return &s
}
// Merge adds rules from 'other' to 's'.
// Rules for the same class are merged (new fields added, existing fields updated).
func (s *Schema) Merge(other *Schema) {
if other == nil {
return
}
for className, classDef := range other.Classes {
if existingClass, ok := s.Classes[className]; ok {
// Merge fields
fieldMap := make(map[string]FieldDefinition)
for _, f := range classDef.Fields {
fieldMap[f.Name] = f
}
var mergedFields []FieldDefinition
seen := make(map[string]bool)
// Keep existing fields, update if present in other
for _, f := range existingClass.Fields {
if newF, ok := fieldMap[f.Name]; ok {
mergedFields = append(mergedFields, newF)
} else {
mergedFields = append(mergedFields, f)
}
seen[f.Name] = true
}
// Append new fields
for _, f := range classDef.Fields {
if !seen[f.Name] {
mergedFields = append(mergedFields, f)
}
}
existingClass.Fields = mergedFields
if classDef.Ordered {
existingClass.Ordered = true
}
if classDef.Direction != "" {
existingClass.Direction = classDef.Direction
}
s.Classes[className] = existingClass
} else {
s.Classes[className] = classDef
}
return cue.Value{}, err
}
return ctx.CompileBytes(content), nil
}
func LoadFullSchema(projectRoot string) *Schema {
s := DefaultSchema()
ctx := cuecontext.New()
baseVal := ctx.CompileBytes(defaultSchemaCUE)
if baseVal.Err() != nil {
// Fallback or panic? Panic is appropriate for embedded schema failure
panic(fmt.Sprintf("Embedded schema invalid: %v", baseVal.Err()))
}
// 1. System Paths
sysPaths := []string{
"/usr/share/mdt/marte_schema.json",
"/usr/share/mdt/marte_schema.cue",
}
home, err := os.UserHomeDir()
if err == nil {
sysPaths = append(sysPaths, filepath.Join(home, ".local/share/mdt/marte_schema.json"))
sysPaths = append(sysPaths, filepath.Join(home, ".local/share/mdt/marte_schema.cue"))
}
for _, path := range sysPaths {
if sysSchema, err := LoadSchema(path); err == nil {
s.Merge(sysSchema)
if val, err := LoadSchema(ctx, path); err == nil && val.Err() == nil {
baseVal = baseVal.Unify(val)
}
}
// 2. Project Path
if projectRoot != "" {
projectSchemaPath := filepath.Join(projectRoot, ".marte_schema.json")
if projSchema, err := LoadSchema(projectSchemaPath); err == nil {
s.Merge(projSchema)
projectSchemaPath := filepath.Join(projectRoot, ".marte_schema.cue")
if val, err := LoadSchema(ctx, projectSchemaPath); err == nil && val.Err() == nil {
baseVal = baseVal.Unify(val)
}
}
return s
return &Schema{
Context: ctx,
Value: baseVal,
}
}