Skip to content

Commit e82e77e

Browse files
author
mirkobrombin
committed
feat: add manifest version support and validation command for cpak.json
1 parent 89a12d7 commit e82e77e

5 files changed

Lines changed: 82 additions & 20 deletions

File tree

cmd/init.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ func NewInitCommand() *cobra.Command {
1919
}
2020

2121
// flags required...
22+
cmd.Flags().StringP("manifest-version", "m", "1.0", "Manifest version (default: 1.0)")
23+
2224
cmd.Flags().StringP("name", "n", "", "Name of the application (required)")
2325
cmd.MarkFlagRequired("name")
2426

@@ -43,6 +45,7 @@ func NewInitCommand() *cobra.Command {
4345

4446
// initRun executes the scaffolding of cpak.json based on provided flags.
4547
func initRun(cmd *cobra.Command, args []string) error {
48+
manifestVersion, _ := cmd.Flags().GetString("manifest-version")
4649
name, _ := cmd.Flags().GetString("name")
4750
version, _ := cmd.Flags().GetString("version")
4851
desc, _ := cmd.Flags().GetString("description")
@@ -54,16 +57,17 @@ func initRun(cmd *cobra.Command, args []string) error {
5457
idle, _ := cmd.Flags().GetInt("idle-time")
5558

5659
manifest := types.CpakManifest{
57-
Name: name,
58-
Description: desc,
59-
Version: version,
60-
Image: image,
61-
Binaries: binaries,
62-
DesktopEntries: desktops,
63-
Dependencies: []types.Dependency{},
64-
Addons: addons,
65-
IdleTime: idle,
66-
Override: types.NewOverride(),
60+
ManifestVersion: manifestVersion,
61+
Name: name,
62+
Description: desc,
63+
Version: version,
64+
Image: image,
65+
Binaries: binaries,
66+
DesktopEntries: desktops,
67+
Dependencies: []types.Dependency{},
68+
Addons: addons,
69+
IdleTime: idle,
70+
Override: types.NewOverride(),
6771
}
6872
for _, origin := range deps {
6973
manifest.Dependencies = append(manifest.Dependencies, types.Dependency{Origin: origin})

cmd/validate.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/invopop/jsonschema"
8+
"github.com/mirkobrombin/cpak/pkg/types"
9+
"github.com/spf13/cobra"
10+
"github.com/xeipuuv/gojsonschema"
11+
)
12+
13+
// NewValidateCommand creates the `validate` command for verifying a cpak.json
14+
// manifest against the JSON Schema.
15+
func NewValidateCommand() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "validate [manifest]",
18+
Short: "Validate a cpak.json manifest against manifest.schema.json",
19+
Args: cobra.ExactArgs(1),
20+
RunE: runValidate,
21+
}
22+
return cmd
23+
}
24+
25+
// runValidate checks the provided manifest against the JSON Schema and reports
26+
// any validation errors.
27+
func runValidate(cmd *cobra.Command, args []string) error {
28+
manifestPath := args[0]
29+
30+
reflector := &jsonschema.Reflector{ExpandedStruct: true}
31+
schema := reflector.Reflect(&types.CpakManifest{})
32+
33+
schemaBytes, err := json.Marshal(schema)
34+
if err != nil {
35+
return fmt.Errorf("failed to serialize schema: %w", err)
36+
}
37+
schemaLoader := gojsonschema.NewBytesLoader(schemaBytes)
38+
documentLoader := gojsonschema.NewReferenceLoader("file://" + manifestPath)
39+
40+
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
41+
if err != nil {
42+
return fmt.Errorf("schema validation error: %w", err)
43+
}
44+
45+
if !result.Valid() {
46+
fmt.Println("Manifest validation errors:")
47+
for _, desc := range result.Errors() {
48+
fmt.Printf(" - %s\n", desc)
49+
}
50+
return fmt.Errorf("validation failed with %d errors", len(result.Errors()))
51+
}
52+
53+
fmt.Println("Manifest is valid against the schema.")
54+
return nil
55+
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func main() {
3636
rootCmd.AddCommand(cmd.NewExtractCommand())
3737
rootCmd.AddCommand(cmd.NewInitCommand())
3838
rootCmd.AddCommand(cmd.NewGenSchemaCommand())
39+
rootCmd.AddCommand(cmd.NewValidateCommand())
3940
rootCmd.AddCommand(cmd.NewHostExecServerCommand())
4041
rootCmd.AddCommand(cmd.NewHostExecClientCommand())
4142

pkg/cpak/validator.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7-
"os"
8-
"path/filepath"
97
"strings"
108

9+
"github.com/invopop/jsonschema"
1110
"github.com/mirkobrombin/cpak/pkg/types"
1211
"github.com/xeipuuv/gojsonschema"
1312
)
1413

1514
// ValidateManifest validates a CpakManifest against its JSON schema.
1615
func ValidateManifest(m *types.CpakManifest) error {
17-
exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
16+
reflector := &jsonschema.Reflector{ExpandedStruct: true}
17+
schema := reflector.Reflect(&types.CpakManifest{})
18+
19+
schemaBytes, err := json.Marshal(schema)
1820
if err != nil {
19-
return fmt.Errorf("cannot locate executable dir: %w", err)
20-
}
21-
schemaPath := filepath.Join(exeDir, "manifest.schema.json")
22-
if _, err := os.Stat(schemaPath); err != nil {
23-
return fmt.Errorf("schema file not found at %s: %w", schemaPath, err)
21+
return fmt.Errorf("failed to serialize JSON schema: %w", err)
2422
}
25-
26-
schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)
23+
schemaLoader := gojsonschema.NewBytesLoader(schemaBytes)
2724

2825
manifestBytes, err := json.Marshal(m)
2926
if err != nil {
@@ -35,6 +32,7 @@ func ValidateManifest(m *types.CpakManifest) error {
3532
if err != nil {
3633
return fmt.Errorf("schema validation error: %w", err)
3734
}
35+
3836
if !result.Valid() {
3937
var sb strings.Builder
4038
sb.WriteString("manifest validation failed:\n")
@@ -45,5 +43,6 @@ func ValidateManifest(m *types.CpakManifest) error {
4543
}
4644
return errors.New(sb.String())
4745
}
46+
4847
return nil
4948
}

pkg/types/manifest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ package types
77

88
// CpakManifest is the struct that represents the manifest of an application.
99
type CpakManifest struct {
10+
// ManifestVersion is the version of the manifest schema (e.g. "1.0").
11+
ManifestVersion string `json:"manifest_version" jsonschema:"enum=1.0,description=Manifest schema version"`
12+
1013
// Name is the name of the application.
1114
Name string `json:"name" jsonschema:"minLength=1,description=Application name"`
1215

0 commit comments

Comments
 (0)