11package cmd
22
33import (
4+ "encoding/json"
45 "fmt"
56 "os"
67 "os/user"
78 "strings"
89
910 "github.com/actionforge/actrun-cli/core"
1011 u "github.com/actionforge/actrun-cli/utils"
12+ "github.com/santhosh-tekuri/jsonschema/v6"
1113 "github.com/spf13/cobra"
1214 "go.yaml.in/yaml/v4"
1315)
1416
17+ // ActfileSchema holds the embedded JSON schema bytes, set from main.
18+ var ActfileSchema []byte
19+
1520var cmdValidate = & cobra.Command {
1621 Use : "validate [graph-file]" ,
1722 Short : "Validate a graph file." ,
@@ -38,6 +43,56 @@ var cmdValidate = &cobra.Command{
3843 },
3944}
4045
46+ func validateSchema (data any ) error {
47+ if len (ActfileSchema ) == 0 {
48+ return fmt .Errorf ("actfile schema not loaded" )
49+ }
50+
51+ var schemaObj any
52+ if err := json .Unmarshal (ActfileSchema , & schemaObj ); err != nil {
53+ return fmt .Errorf ("failed to parse schema JSON: %w" , err )
54+ }
55+
56+ compiler := jsonschema .NewCompiler ()
57+ if err := compiler .AddResource ("actfile-schema.json" , schemaObj ); err != nil {
58+ return fmt .Errorf ("failed to add schema resource: %w" , err )
59+ }
60+
61+ schema , err := compiler .Compile ("actfile-schema.json" )
62+ if err != nil {
63+ return fmt .Errorf ("failed to compile schema: %w" , err )
64+ }
65+
66+ return schema .Validate (convertToJSONCompatible (data ))
67+ }
68+
69+ // convertToJSONCompatible recursively converts YAML-unmarshalled data into
70+ // types that the JSON schema validator accepts.
71+ func convertToJSONCompatible (v any ) any {
72+ switch val := v .(type ) {
73+ case map [string ]any :
74+ result := make (map [string ]any , len (val ))
75+ for k , v := range val {
76+ result [k ] = convertToJSONCompatible (v )
77+ }
78+ return result
79+ case []any :
80+ result := make ([]any , len (val ))
81+ for i , v := range val {
82+ result [i ] = convertToJSONCompatible (v )
83+ }
84+ return result
85+ case int :
86+ return float64 (val )
87+ case int64 :
88+ return float64 (val )
89+ case float32 :
90+ return float64 (val )
91+ default :
92+ return val
93+ }
94+ }
95+
4196func validateGraph (filePath string ) error {
4297 fmt .Printf ("Validating '%s'...\n " , filePath )
4398
@@ -54,10 +109,17 @@ func validateGraph(filePath string) error {
54109 return err
55110 }
56111
112+ hasErrors := false
113+
114+ if err := validateSchema (graphYaml ); err != nil {
115+ fmt .Printf ("\n ❌ Graph schema validation failed:\n %v\n " , err )
116+ hasErrors = true
117+ }
118+
57119 _ , errs := core .LoadGraph (graphYaml , nil , "" , true , core.RunOpts {})
58120
59121 if len (errs ) > 0 {
60- fmt .Printf ("\n ❌ Validation failed with %d error(s):\n " , len (errs ))
122+ fmt .Printf ("\n ❌ Graph validation failed with %d error(s):\n " , len (errs ))
61123
62124 for i , e := range errs {
63125 if leafErr , ok := e .(* core.LeafError ); ok {
@@ -67,6 +129,10 @@ func validateGraph(filePath string) error {
67129 fmt .Printf ("\n %d. %v\n " , i + 1 , e )
68130 }
69131 }
132+ hasErrors = true
133+ }
134+
135+ if hasErrors {
70136 return fmt .Errorf ("validation failed" )
71137 }
72138
@@ -84,6 +150,17 @@ func expandPath(path string) string {
84150 return os .ExpandEnv (path )
85151}
86152
153+ var cmdSchema = & cobra.Command {
154+ Use : "schema" ,
155+ Short : "Print the JSON schema for .act files." ,
156+ Long : `Prints the JSON schema used to validate ActionForge graph (.act) files.` ,
157+ Args : cobra .NoArgs ,
158+ Run : func (cmd * cobra.Command , args []string ) {
159+ fmt .Println (string (ActfileSchema ))
160+ },
161+ }
162+
87163func init () {
88164 cmdRoot .AddCommand (cmdValidate )
165+ cmdRoot .AddCommand (cmdSchema )
89166}
0 commit comments