From 615df880d3f7d93b713a53d0f68f361795b9c06c Mon Sep 17 00:00:00 2001
From: Gabriele Fontana <gafreax@gmail.com>
Date: Sun, 24 Nov 2024 21:52:57 +0100
Subject: [PATCH] Feat/add contain value rule (#103)

* feat(add-contain-value-rule): add contain_value expression

* refactor(add-contain-value-rule): renaming

* test(add-contain-value-rule): add test for contain_value expression

* feat(add-contain-value-rule): add fileThatContainValue schema definition

* feat(add-contain-value-rule): add Value field in That type

* test(add-contain-value-rule): add taht contain rule example
---
 api/config_schema.json                        | 17 ++++++
 examples/.goarkitect.yaml                     | 12 +++++
 internal/arch/file/that/contain_value.go      | 43 +++++++++++++++
 internal/arch/file/that/contain_value_test.go | 52 +++++++++++++++++++
 internal/config/executor.go                   |  4 ++
 5 files changed, 128 insertions(+)
 create mode 100644 internal/arch/file/that/contain_value.go
 create mode 100644 internal/arch/file/that/contain_value_test.go

diff --git a/api/config_schema.json b/api/config_schema.json
index 3edb181..72b84c2 100644
--- a/api/config_schema.json
+++ b/api/config_schema.json
@@ -96,6 +96,23 @@
                 "folder"
             ]
         },
+        "fileThatContainValue": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "kind": {
+                    "type": "string",
+                    "pattern": "^contain_value$"
+                },
+                "value": {
+                    "type": "string"
+                }
+            },
+            "required": [
+                "kind",
+                "value"
+            ]
+        },
         "fileThatEndWith": {
             "type": "object",
             "additionalProperties": false,
diff --git a/examples/.goarkitect.yaml b/examples/.goarkitect.yaml
index 7413639..0767edb 100644
--- a/examples/.goarkitect.yaml
+++ b/examples/.goarkitect.yaml
@@ -69,3 +69,15 @@ rules:
       - kind: end_with
         suffix: file
     because: "it is an example"
+  - name: a set of file that contains .go must be go file (end with .go) 
+    kind: file
+    matcher:
+      kind: all
+    thats:
+      - kind: contain_value
+        value: .go
+    excepts: []
+    shoulds:
+      - kind: end_with
+        suffix: .go
+    because: "it is an example "
diff --git a/internal/arch/file/that/contain_value.go b/internal/arch/file/that/contain_value.go
new file mode 100644
index 0000000..c2f0b2e
--- /dev/null
+++ b/internal/arch/file/that/contain_value.go
@@ -0,0 +1,43 @@
+package that
+
+import (
+	"strings"
+
+	"github.com/omissis/goarkitect/internal/arch/file"
+	"github.com/omissis/goarkitect/internal/arch/rule"
+)
+
+func ContainValue(s string) *ContainValueExpression {
+	return &ContainValueExpression{
+		value: s,
+	}
+}
+
+type ContainValueExpression struct {
+	value string
+
+	errors []error
+}
+
+func (e *ContainValueExpression) GetErrors() []error {
+	return e.errors
+}
+
+func (e *ContainValueExpression) Evaluate(rb rule.Builder) {
+	frb, ok := rb.(*file.RuleBuilder)
+	if !ok {
+		e.errors = append(e.errors, file.ErrInvalidRuleBuilder)
+
+		return
+	}
+
+	files := make([]string, 0)
+
+	for _, f := range frb.GetFiles() {
+		if strings.Contains(f, e.value) {
+			files = append(files, f)
+		}
+	}
+
+	frb.SetFiles(files)
+}
diff --git a/internal/arch/file/that/contain_value_test.go b/internal/arch/file/that/contain_value_test.go
new file mode 100644
index 0000000..2ea34e2
--- /dev/null
+++ b/internal/arch/file/that/contain_value_test.go
@@ -0,0 +1,52 @@
+package that_test
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+
+	"github.com/omissis/goarkitect/internal/arch/file"
+	"github.com/omissis/goarkitect/internal/arch/file/that"
+	"github.com/omissis/goarkitect/internal/arch/rule"
+)
+
+func Test_ContainValue(t *testing.T) {
+	t.Parallel()
+
+	rb := func() *file.RuleBuilder {
+		rb := file.All()
+		rb.SetFiles([]string{"Dockerfile", "Makefile", "foo/bar.go"})
+
+		return rb
+	}
+
+	testCases := []struct {
+		desc        string
+		ruleBuilder *file.RuleBuilder
+		value       string
+		want        []string
+	}{
+		{
+			desc:        "files contains value 'foo'",
+			ruleBuilder: rb(),
+			value:       "foo",
+			want:        []string{"foo/bar.go"},
+		},
+	}
+	for _, tC := range testCases {
+		tC := tC
+
+		t.Run(tC.desc, func(t *testing.T) {
+			t.Parallel()
+
+			ew := that.ContainValue(tC.value)
+			ew.Evaluate(tC.ruleBuilder)
+
+			got := tC.ruleBuilder.GetFiles()
+			if !cmp.Equal(got, tC.want, cmp.AllowUnexported(rule.Violation{}), cmpopts.EquateEmpty()) {
+				t.Errorf("want = %+v, got = %+v", tC.want, got)
+			}
+		})
+	}
+}
diff --git a/internal/config/executor.go b/internal/config/executor.go
index 00b64fe..99c14d3 100644
--- a/internal/config/executor.go
+++ b/internal/config/executor.go
@@ -44,6 +44,7 @@ type That struct {
 	Folder    string `json:"folder"    yaml:"folder"`
 	Recursive bool   `json:"recursive" yaml:"recursive"`
 	Suffix    string `json:"suffix"    yaml:"suffix"`
+	Value     string `json:"value"     yaml:"value"`
 }
 type Except struct {
 	Kind     string `json:"kind"     yaml:"kind"`
@@ -158,6 +159,9 @@ func applyThats(rb *file.RuleBuilder, ts []That) error {
 		case "end_with":
 			rb.That(ft.EndWith(t.Suffix))
 
+		case "contain_value":
+			rb.That(ft.ContainValue(t.Value))
+
 		default:
 			return fmt.Errorf("'%s': %w", t.Kind, ErrUnknonwnThat)
 		}