Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add API for checker and support mod checking in update #557

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 41 additions & 28 deletions pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,57 @@ type Checker interface {
Check(pkg.KclPkg) error
}

// DepChecker is responsible for running multiple checkers on a package's dependencies.
type DepChecker struct {
// ModChecker is responsible for running multiple checkers on a KCL module.
type ModChecker struct {
checkers []Checker
}

// DepCheckerOption configures how we set up DepChecker.
type DepCheckerOption func(*DepChecker)
// ModCheckerOption configures how we set up ModChecker.
type ModCheckerOption func(*ModChecker)

// NewDepChecker creates a new DepChecker with options.
func NewDepChecker(options ...DepCheckerOption) *DepChecker {
depChecker := &DepChecker{}
// NewModChecker creates a new ModChecker with options.
func NewModChecker(options ...ModCheckerOption) *ModChecker {
ModChecker := &ModChecker{}
for _, opt := range options {
opt(depChecker)
opt(ModChecker)
}
return depChecker
return ModChecker
}

// WithChecker adds a single Checker to DepChecker.
func WithChecker(checker Checker) DepCheckerOption {
return func(c *DepChecker) {
// WithChecker adds a single Checker to ModChecker.
func WithChecker(checker Checker) ModCheckerOption {
return func(c *ModChecker) {
if c.checkers == nil {
c.checkers = []Checker{}
}
c.checkers = append(c.checkers, checker)
}
}

// WithCheckers adds multiple Checkers to DepChecker.
func WithCheckers(checkers ...Checker) DepCheckerOption {
return func(c *DepChecker) {
// WithCheckers adds multiple Checkers to ModChecker.
func WithCheckers(checkers ...Checker) ModCheckerOption {
return func(c *ModChecker) {
if c.checkers == nil {
c.checkers = []Checker{}
}
c.checkers = append(c.checkers, checkers...)
}
}

func (mc *ModChecker) AddChecker(checker Checker) {
mc.checkers = append(mc.checkers, checker)
}

func (mc *ModChecker) CheckersSize() int {
if mc.checkers == nil {
return 0
}
return len(mc.checkers)
}

// Check runs all individual checks for a kclPkg.
func (dc *DepChecker) Check(kclPkg pkg.KclPkg) error {
for _, checker := range dc.checkers {
func (mc *ModChecker) Check(kclPkg pkg.KclPkg) error {
for _, checker := range mc.checkers {
if err := checker.Check(kclPkg); err != nil {
return err
}
Expand All @@ -75,11 +92,8 @@ func NewIdentChecker() *IdentChecker {
}

func (ic *IdentChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyName(dep.Name) {
return fmt.Errorf("invalid dependency name: %s", dep.Name)
}
if !isValidDependencyName(kclPkg.ModFile.Pkg.Name) {
return fmt.Errorf("invalid name: %s", kclPkg.ModFile.Pkg.Name)
}
return nil
}
Expand All @@ -93,12 +107,11 @@ func NewVersionChecker() *VersionChecker {
}

func (vc *VersionChecker) Check(kclPkg pkg.KclPkg) error {
for _, key := range kclPkg.Dependencies.Deps.Keys() {
dep, _ := kclPkg.Dependencies.Deps.Get(key)
if !isValidDependencyVersion(dep.Version) {
return fmt.Errorf("invalid dependency version: %s for %s", dep.Version, dep.Name)
}
if !isValidDependencyVersion(kclPkg.ModFile.Pkg.Version) {
return fmt.Errorf("invalid version: %s for %s",
kclPkg.ModFile.Pkg.Version, kclPkg.ModFile.Pkg.Name)
}

return nil
}

Expand Down Expand Up @@ -147,7 +160,7 @@ func (sc *SumChecker) Check(kclPkg pkg.KclPkg) error {

// isValidDependencyName checks whether the given dependency name is valid.
func isValidDependencyName(name string) bool {
validNamePattern := `^[a-zA-Z][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_]$`
validNamePattern := `^[a-z][a-z0-9_]*(?:-[a-z0-9_]+)*$`
regex := regexp.MustCompile(validNamePattern)
return regex.MatchString(name)
}
Expand Down
90 changes: 31 additions & 59 deletions pkg/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"kcl-lang.io/kpm/pkg/settings"
)

func TestDepCheckerCheck(t *testing.T) {
depChecker := NewDepChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker()))
func TestModCheckerCheck(t *testing.T) {
ModChecker := NewModChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker()))

deps1 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps1.Set("kcl1", pkg.Dependency{
Expand All @@ -31,22 +31,6 @@ func TestDepCheckerCheck(t *testing.T) {
Sum: "no-sum-check-enabled",
})

deps2 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps2.Set("kcl1", pkg.Dependency{
Name: ".kcl1",
FullName: "kcl1",
Version: "0.0.1",
Sum: "",
})

deps3 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps3.Set("kcl1", pkg.Dependency{
Name: "kcl1",
FullName: "kcl1",
Version: "1.0.0-alpha#",
Sum: "",
})

tests := []struct {
name string
KclPkg pkg.KclPkg
Expand All @@ -56,6 +40,10 @@ func TestDepCheckerCheck(t *testing.T) {
name: "valid kcl package - with no sum check enabled",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -66,40 +54,12 @@ func TestDepCheckerCheck(t *testing.T) {
},
wantErr: false,
},
{
name: "Invalid kcl package - invalid dependency name",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps2,
},
NoSumCheck: false,
},
wantErr: true,
},
{
name: "Invalid kcl package - invalid dependency version",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Dependencies: pkg.Dependencies{
Deps: deps3,
},
NoSumCheck: false,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr := depChecker.Check(tt.KclPkg)
gotErr := ModChecker.Check(tt.KclPkg)
if (gotErr != nil) != tt.wantErr {
t.Errorf("depChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
t.Errorf("ModChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
}
})
}
Expand All @@ -112,12 +72,12 @@ func TestIsValidDependencyName(t *testing.T) {
want bool
}{
{"Empty Name", "", false},
{"Valid Name - Simple", "myDependency", true},
{"Valid Name - Simple", "myDependency", false},
{"Valid Name - With Underscore", "my_dependency", true},
{"Valid Name - With Hyphen", "my-dependency", true},
{"Valid Name - With Dot", "my.dependency", true},
{"Valid Name - Mixed Case", "MyDependency", true},
{"Valid Name - Long Name", "My_Very-Long.Dependency", true},
{"Valid Name - With Dot", "my.dependency", false},
{"Valid Name - Mixed Case", "MyDependency", false},
{"Valid Name - Long Name", "My_Very-Long.Dependency", false},
{"Contains Number", "depend3ncy", true},
{"Starts with Special Character", "-dependency", false},
{"Starts and Ends with Dot", ".dependency.", false},
Expand Down Expand Up @@ -184,9 +144,9 @@ func getTestSettings() (*settings.Settings, error) {
return settings, nil
}

func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
func TestModCheckerCheck_WithTrustedSum(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping TestDepCheckerCheck_WithTrustedSum test on Windows")
t.Skip("Skipping TestModCheckerCheck_WithTrustedSum test on Windows")
}

// Start the local Docker registry required for testing
Expand All @@ -197,12 +157,12 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
err = mock.PushTestPkgToRegistry()
assert.Equal(t, err, nil)

// Initialize settings for use with the DepChecker
// Initialize settings for use with the ModChecker
settings, err := getTestSettings()
assert.Equal(t, err, nil)

// Initialize the DepChecker with required checkers
depChecker := NewDepChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker(WithSettings(*settings))))
// Initialize the ModChecker with required checkers
ModChecker := NewModChecker(WithCheckers(NewIdentChecker(), NewVersionChecker(), NewSumChecker(WithSettings(*settings))))

deps1 := orderedmap.NewOrderedMap[string, pkg.Dependency]()
deps1.Set("kcl1", pkg.Dependency{
Expand Down Expand Up @@ -243,6 +203,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "valid kcl package - with sum check",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -257,6 +221,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "valid kcl package - with no sum check enabled",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -271,6 +239,10 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {
name: "Invalid kcl package - with no sum check disabled - checksum mismatches",
KclPkg: pkg.KclPkg{
ModFile: pkg.ModFile{
Pkg: pkg.Package{
Name: "testmod",
Version: "0.0.1",
},
HomePath: "path/to/modfile",
},
HomePath: "path/to/kcl/pkg",
Expand All @@ -285,9 +257,9 @@ func TestDepCheckerCheck_WithTrustedSum(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr := depChecker.Check(tt.KclPkg)
gotErr := ModChecker.Check(tt.KclPkg)
if (gotErr != nil) != tt.wantErr {
t.Errorf("depChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
t.Errorf("ModChecker.Check(%v) = %v, want error %v", tt.KclPkg, gotErr, tt.wantErr)
}
})
}
Expand Down
57 changes: 57 additions & 0 deletions pkg/client/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package client

import (
"fmt"

"kcl-lang.io/kpm/pkg/checker"
pkg "kcl-lang.io/kpm/pkg/package"
)

type CheckOptions struct {
KclMod *pkg.KclPkg
}

type CheckOption func(*CheckOptions) error

func WithCheckKclMod(kclMod *pkg.KclPkg) CheckOption {
return func(opts *CheckOptions) error {
if kclMod == nil {
return fmt.Errorf("kclMod cannot be nil")
}
opts.KclMod = kclMod
return nil
}
}

func (c *KpmClient) Check(options ...CheckOption) error {
opts := &CheckOptions{}
for _, option := range options {
if err := option(opts); err != nil {
return err
}
}

kmod := opts.KclMod
if kmod == nil {
return fmt.Errorf("kclMod cannot be nil")
}

// Init the ModChecker, name and version checkers are required.
if c.ModChecker == nil || c.ModChecker.CheckersSize() == 0 {
c.ModChecker = checker.NewModChecker(
checker.WithCheckers(
checker.NewIdentChecker(),
checker.NewVersionChecker(),
checker.NewSumChecker(),
),
)
}

// Check the module and the dependencies
err := c.ModChecker.Check(*kmod)
if err != nil {
return err
}

return err
}
Loading