Skip to content

Commit

Permalink
feat: add file/should/have_permission expression, replace some panics…
Browse files Browse the repository at this point in the history
… with errors
  • Loading branch information
omissis committed Aug 12, 2022
1 parent 6712dc4 commit 77e3ceb
Show file tree
Hide file tree
Showing 22 changed files with 287 additions and 85 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ This project gives developers the ability to describe and check the architecture

## TODO

- add docs to tell what options each expression support (even better: enforce that using type system)
- [ ] add docs to tell what options each expression support (even better: enforce that using type system)
- [ ] replace panics with better error handling
- [ ] review rule builder locking

## Desired usecases

Expand All @@ -13,8 +15,12 @@ if a folder:
- [ ] does not exist
- [ ] contains a specific file
- [ ] contains a specific set of files
- [ ] contains a files matching a regex
- [ ] contains a files matching a glob pattern
- [ ] contains files matching a regex
- [ ] contains files matching a glob pattern
- [ ] contains only a specific file
- [ ] contains only a specific set of files
- [ ] contains only files matching a regex
- [ ] contains only files matching a glob pattern
- [ ] is gitignored
- [ ] is gitcrypted
- [ ] has specific permissions
Expand All @@ -30,7 +36,7 @@ if a file:
- [x] content contains a value
- [x] is gitignored
- [x] is gitcrypted
- [ ] has specific permissions
- [x] has specific permissions

if a set of files:
- [x] exists
Expand Down
9 changes: 8 additions & 1 deletion internal/arch/file/except/except.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import (

type Expression interface {
Evaluate(rb rule.Builder)
GetErrors() []error
}

type evaluateFunc func(filePath string) bool

type baseExpression struct{}
type baseExpression struct {
errors []error
}

func (e *baseExpression) GetErrors() []error {
return e.errors
}

func (e baseExpression) evaluate(rb rule.Builder, eval evaluateFunc) {
frb := rb.(*file.RuleBuilder)
Expand Down
15 changes: 13 additions & 2 deletions internal/arch/file/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,27 @@ func (rb *RuleBuilder) Because(b rule.Because) ([]rule.Violation, []error) {
rb.because = b

for _, that := range rb.thats {
if len(that.GetErrors()) > 0 {
return nil, that.GetErrors()
}

that.Evaluate(rb)
}

for _, except := range rb.excepts {
if len(except.GetErrors()) > 0 {
return nil, except.GetErrors()
}

except.Evaluate(rb)
}

for _, should := range rb.shoulds {
vs := should.Evaluate(rb)
if len(vs) > 0 {
if len(should.GetErrors()) > 0 {
return nil, should.GetErrors()
}

if vs := should.Evaluate(rb); len(vs) > 0 {
rb.violations = append(rb.violations, vs...)
}
}
Expand Down
4 changes: 3 additions & 1 deletion internal/arch/file/should/be_gitencrypted.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func (e gitEncryptedExpression) doEvaluate(rb rule.Builder, filePath string) boo
cmd := exec.Command("git", "crypt", "status", filePath)
out, err := cmd.CombinedOutput()
if err != nil {
panic(err)
rb.AddError(err)

return true
}

return bytes.Contains(out, []byte("not encrypted"))
Expand Down
10 changes: 3 additions & 7 deletions internal/arch/file/should/be_gitencrypted_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,16 @@ func Test_BeGitencrypted(t *testing.T) {
{
desc: "negated: file 'encrypted.txt' should not be gitencrypted",
ruleBuilder: file.One(filepath.Join(basePath, "test/encrypted.txt")),
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
want: []rule.Violation{
rule.NewViolation("file 'encrypted.txt' is gitencrypted"),
},
},
{
desc: "negated: file 'not_encrypted.txt' should not be gitencrypted",
ruleBuilder: file.One(filepath.Join(basePath, "test/not_encrypted.txt")),
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
}

Expand Down
4 changes: 3 additions & 1 deletion internal/arch/file/should/be_gitignored.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ func (e gitIgnoredExpression) doEvaluate(rb rule.Builder, filePath string) bool
case *exec.ExitError:
return err.(*exec.ExitError).ExitCode() != 0
default:
panic(err)
rb.AddError(err)

return true
}
}

Expand Down
10 changes: 3 additions & 7 deletions internal/arch/file/should/be_gitignored_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,16 @@ func Test_BeGitignored(t *testing.T) {
{
desc: "negated: file 'ignored.txt' should not be gitignored",
ruleBuilder: file.One(filepath.Join(basePath, "test/ignored.txt")),
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
want: []rule.Violation{
rule.NewViolation("file 'ignored.txt' is gitignored"),
},
},
{
desc: "negated: file 'not_ignored.txt' should not be gitignored",
ruleBuilder: file.One(filepath.Join(basePath, "test/not_ignored.txt")),
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
}

Expand Down
10 changes: 3 additions & 7 deletions internal/arch/file/should/contain_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ func Test_ContainValue(t *testing.T) {
desc: "negated: file 'foobar.txt' contains the value 'bar'",
ruleBuilder: file.One(filepath.Join(basePath, "test/foobar.txt")),
value: "bar",
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
want: []rule.Violation{
rule.NewViolation("file 'foobar.txt' does contain the value 'bar'"),
},
Expand All @@ -75,10 +73,8 @@ func Test_ContainValue(t *testing.T) {
desc: "negated: file 'foobar.txt' does not contain the value 'something else'",
ruleBuilder: file.One(filepath.Join(basePath, "test/foobar.txt")),
value: "something else",
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
}

Expand Down
10 changes: 3 additions & 7 deletions internal/arch/file/should/end_with_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,14 @@ func Test_EndWith(t *testing.T) {
desc: "negated: foobar does not end with baz",
ruleBuilder: file.One("foobar"),
suffix: "baz",
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
{
desc: "negated: foobar ends with bar",
ruleBuilder: file.One("foobar"),
suffix: "bar",
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
want: []rule.Violation{
rule.NewViolation("file's name 'foobar' does end with 'bar'"),
},
Expand Down
8 changes: 2 additions & 6 deletions internal/arch/file/should/exist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,15 @@ func Test_Exist(t *testing.T) {
{
desc: "negated: exist.go exists",
ruleBuilder: file.One("exist.go"),
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
want: []rule.Violation{
rule.NewViolation("file 'exist.go' does exist"),
},
},
{
desc: "negated: abc.xyz does not exist",
ruleBuilder: file.One("abc.xyz"),
options: []should.Option{
should.Negated{},
},
options: []should.Option{should.Negated{}},
},
}
for _, tC := range testCases {
Expand Down
6 changes: 2 additions & 4 deletions internal/arch/file/should/have_content_matching_regex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,8 @@ func Test_HaveContentMatchingRegex(t *testing.T) {
desc: "negated: content of file 'foobar.txt' does not match regex",
ruleBuilder: file.One(filepath.Join(basePath, "test/foobar.txt")),
regexp: "^something\\ else.+",
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
}

Expand Down
6 changes: 2 additions & 4 deletions internal/arch/file/should/have_content_matching_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,8 @@ func Test_HaveContentMatching(t *testing.T) {
desc: "negated: content of file 'foobar.txt' does not match expected content",
ruleBuilder: file.One(filepath.Join(basePath, "test/foobar.txt")),
content: "something else\n",
options: []should.Option{
should.Negated{},
},
want: nil,
options: []should.Option{should.Negated{}},
want: nil,
},
}

Expand Down
76 changes: 76 additions & 0 deletions internal/arch/file/should/have_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package should

import (
"errors"
"fmt"
"goarkitect/internal/arch/rule"
"os"
"path/filepath"
"regexp"
)

var (
ErrInvalidPermissions = errors.New(
"permissions must only contain the following characters: 'd', 'r', 'w', 'x', '-'",
)
)

func HavePermissions(want string, opts ...Option) *havePermissionsExpression {
rx := regexp.MustCompile("[d-][rwx-]{9}")

errs := make([]error, 0)
if !rx.MatchString(want) {
errs = append(errs, ErrInvalidPermissions)
}

expr := &havePermissionsExpression{
want: want,
baseExpression: baseExpression{
errors: errs,
},
}

for _, opt := range opts {
opt.apply(&expr.options)
}

return expr
}

type havePermissionsExpression struct {
baseExpression

want string
}

func (e havePermissionsExpression) Evaluate(rb rule.Builder) []rule.Violation {
return e.evaluate(rb, e.doEvaluate, e.getViolation)
}

func (e havePermissionsExpression) doEvaluate(rb rule.Builder, filePath string) bool {
info, err := os.Stat(filePath)
if err != nil {
rb.AddError(err)

return true
}

return e.want != info.Mode().String()
}

func (e havePermissionsExpression) getViolation(filePath string) rule.Violation {
iNodeType := "file"
if info, _ := os.Stat(filePath); info.IsDir() {
iNodeType = "directory"
}

format := "%s '%s' does not have permissions matching '%s'"

if e.options.negated {
format = "%s '%s' does have permissions matching '%s'"
}

return rule.NewViolation(
fmt.Sprintf(format, iNodeType, filepath.Base(filePath), e.want),
)
}
Loading

0 comments on commit 77e3ceb

Please # to comment.