From b5af8729a17a4ee5270ea640097d5a4620cf4e06 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Thu, 12 Sep 2024 15:09:52 +0200
Subject: [PATCH] feat: New BollValue Type #16, enhance pathvalue and update go
 lang to 1.22

---
 .idea/codeStyles/codeStyleConfig.xml     |   5 +
 .idea/vcs.xml                            |   1 +
 configuration.iml                        |   1 +
 devenv.nix                               |   6 +-
 go.mod                                   |  10 +-
 go.sum                                   |   6 +
 import_test.go                           |   2 +
 path-value.go                            |  31 ---
 properties_test.go                       |  12 +-
 value-bool.go                            | 147 ++++++++++++
 value-bool_test.go                       | 273 +++++++++++++++++++++++
 value-path.go                            |  55 +++++
 path-value_test.go => value-path_test.go |   0
 13 files changed, 507 insertions(+), 42 deletions(-)
 create mode 100644 .idea/codeStyles/codeStyleConfig.xml
 delete mode 100644 path-value.go
 create mode 100644 value-bool.go
 create mode 100644 value-bool_test.go
 create mode 100644 value-path.go
 rename path-value_test.go => value-path_test.go (100%)

diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1dd..f680695 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,5 +2,6 @@
 <project version="4">
   <component name="VcsDirectoryMappings">
     <mapping directory="" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/../http-negotiation" vcs="Git" />
   </component>
 </project>
\ No newline at end of file
diff --git a/configuration.iml b/configuration.iml
index 789c0e8..5f86599 100644
--- a/configuration.iml
+++ b/configuration.iml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module type="JAVA_MODULE" version="4">
+  <component name="Go" enabled="true" />
   <component name="NewModuleRootManager" inherit-compiler-output="true">
     <exclude-output />
     <content url="file://$MODULE_DIR$">
diff --git a/devenv.nix b/devenv.nix
index a32c8d7..4320c83 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -166,7 +166,11 @@
   '';
 
   enterShell = ''
-       
+
+    export CGO_CFLAGS=-O2 -U_FORTIFY_SOURCE;CGO_ENABLED=0 
+    export CGO_LDFLAGS=-U_FORTIFY_SOURCE
+
+
 cat <<'EOF' > CONTRIBUTING.md
 # Contributing to schukai GmbH Projects
 
diff --git a/go.mod b/go.mod
index 09471b9..21ab482 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,20 @@
 module gitlab.schukai.com/oss/libraries/go/application/configuration
 
-go 1.21
+go 1.22.0
+
+toolchain go1.22.6
 
 require (
 	github.com/imdario/mergo v1.0.0
 	github.com/kinbiko/jsonassert v1.1.1
 	github.com/magiconair/properties v1.8.7
-	github.com/pelletier/go-toml/v2 v2.2.1
+	github.com/pelletier/go-toml/v2 v2.2.3
 	github.com/r3labs/diff/v3 v3.0.1
 	github.com/stretchr/testify v1.9.0
 	gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.3.2
 	gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.2
 	gitlab.schukai.com/oss/libraries/go/utilities/watch v0.4.0
-	golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
+	golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
 	gopkg.in/yaml.v3 v3.0.1
 )
 
@@ -22,7 +24,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/sys v0.25.0 // indirect
 )
 
 replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16
diff --git a/go.sum b/go.sum
index 24787c6..21693bc 100644
--- a/go.sum
+++ b/go.sum
@@ -15,6 +15,8 @@ github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOS
 github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
 github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg=
@@ -47,12 +49,16 @@ golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcH
 golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/import_test.go b/import_test.go
index 98fbd03..969063f 100644
--- a/import_test.go
+++ b/import_test.go
@@ -15,8 +15,10 @@ func TestReadExample3(t *testing.T) {
 
 	config := struct {
 		Host string
+		Flag bool
 	}{
 		Host: "localhost",
+		Flag: true,
 	}
 
 	c := New(config)
diff --git a/path-value.go b/path-value.go
deleted file mode 100644
index b80e8c5..0000000
--- a/path-value.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2022 schukai GmbH
-// SPDX-License-Identifier: AGPL-3.0
-
-package configuration
-
-import "path"
-
-type pathInterface interface {
-	String() string
-	Dir() string
-	Ext() string
-	Base() string
-}
-
-type PathValue string
-
-func (p PathValue) Dir() string {
-	return path.Dir(string(p))
-}
-
-func (p PathValue) Ext() string {
-	return path.Ext(string(p))
-}
-
-func (p PathValue) String() string {
-	return string(p)
-}
-
-func (p PathValue) Base() string {
-	return path.Base(string(p))
-}
diff --git a/properties_test.go b/properties_test.go
index 9940ba4..b3aed6a 100644
--- a/properties_test.go
+++ b/properties_test.go
@@ -14,7 +14,7 @@ func TestGetMapForProperties(t *testing.T) {
 	config.A = "A"
 	config.B = true
 	config.C.CA = "CA"
-	config.C.CB = true
+	config.C.CB = false
 	config.C.CD.CDA = "CDA"
 
 	s := New(config)
@@ -23,10 +23,10 @@ func TestGetMapForProperties(t *testing.T) {
 
 	assert.Equal(t, len(e), 0)
 
-	assert.Equal(t, m["A"], "A")
-	assert.Equal(t, m["B"], "true")
-	assert.Equal(t, m["C.CA"], "CA")
-	assert.Equal(t, m["C.CB"], "true")
-	assert.Equal(t, m["C.CB.CDA"], "CDA")
+	assert.Equal(t, "A", m["A"])
+	assert.Equal(t, "true", m["B"])
+	assert.Equal(t, "CA", m["C.CA"])
+	assert.Equal(t, "false", m["C.CB"])
+	assert.Equal(t, "CDA", m["C.CB.CDA"])
 
 }
diff --git a/value-bool.go b/value-bool.go
new file mode 100644
index 0000000..cbe42ed
--- /dev/null
+++ b/value-bool.go
@@ -0,0 +1,147 @@
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+package configuration
+
+import (
+	"encoding/json"
+	"errors"
+	"gopkg.in/yaml.v3"
+	"strings"
+)
+
+type BoolValuer interface {
+	IsTrue() bool
+	IsFalse() bool
+	ToBool() bool
+	IsValid() bool
+	String() string
+	Raw() string
+}
+
+// BoolValue represents a boolean value stored as a string
+type BoolValue struct {
+	value bool // Holds the value bool value
+	valid bool // Tracks if the value is valid ("true" or "false")
+	raw   string
+}
+
+// NewBoolValue creates a new BoolValue and validates it
+func NewBoolValue(raw string) (BoolValue, error) {
+	bv := BoolValue{raw: raw}
+	err := bv.parseAndValidate()
+	return bv, err
+}
+
+// parseAndValidate processes and validates the raw string
+func (p *BoolValue) parseAndValidate() error {
+	switch strings.ToLower(p.raw) {
+	case "true", "yes", "1", "on":
+		p.value = true
+		p.valid = true
+	case "false", "no", "0", "off":
+		p.value = false
+		p.valid = true
+	default:
+		p.valid = false
+		return errors.New("invalid BoolValue: must be 'true' or 'false'")
+	}
+	return nil
+}
+
+// IsTrue returns true if the value represents a logical true
+func (p BoolValue) IsTrue() bool {
+	return p.value && p.valid
+}
+
+// IsFalse returns true if the value represents a logical false
+func (p BoolValue) IsFalse() bool {
+	return !p.IsTrue()
+}
+
+// ToBool converts the BoolValue to a native bool type
+func (p BoolValue) ToBool() bool {
+	return p.IsTrue()
+}
+
+// IsValid checks whether the value is valid
+func (p BoolValue) IsValid() bool {
+	return p.valid
+}
+
+// FromBool converts a native bool to a BoolValue
+func FromBool(b bool) BoolValue {
+	if b {
+		return BoolValue{value: true, valid: true, raw: "true"}
+	}
+	return BoolValue{value: false, valid: true, raw: "false"}
+}
+
+// String returns the raw string value of BoolValue
+func (p BoolValue) Raw() string {
+	return p.raw
+}
+
+// String returns either "true" or "false" or if the value is invalid, the raw value
+func (p BoolValue) String() string {
+	if p.valid {
+		if p.value {
+			return "true"
+		}
+		return "false"
+	}
+
+	return p.raw
+}
+
+// MarshalJSON serializes the BoolValue to JSON
+func (p BoolValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(p.IsTrue())
+}
+
+// UnmarshalJSON deserializes the BoolValue from JSON
+func (p *BoolValue) UnmarshalJSON(data []byte) error {
+	var b string
+	var err error
+	if err = json.Unmarshal(data, &b); err != nil {
+		return err
+	}
+	*p, err = NewBoolValue(b)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// MarshalYAML serializes the BoolValue to YAML
+func (p BoolValue) MarshalYAML() (interface{}, error) {
+	return p.IsTrue(), nil
+}
+
+// UnmarshalYAML deserializes the BoolValue from YAML
+func (p *BoolValue) UnmarshalYAML(value *yaml.Node) error {
+	var b string
+	var err error
+	if err = value.Decode(&b); err != nil {
+		return err
+	}
+	*p, err = NewBoolValue(b)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// MarshalTOML serializes the BoolValue to TOML
+func (p BoolValue) MarshalTOML() ([]byte, error) {
+	return []byte(p.raw), nil
+}
+
+// UnmarshalTOML deserializes the BoolValue from TOML
+func (p *BoolValue) UnmarshalTOML(data []byte) error {
+	p.raw = strings.Trim(string(data), `"`)
+	return p.parseAndValidate()
+}
diff --git a/value-bool_test.go b/value-bool_test.go
new file mode 100644
index 0000000..20dbc04
--- /dev/null
+++ b/value-bool_test.go
@@ -0,0 +1,273 @@
+package configuration
+
+import (
+	"encoding/json"
+	yaml "gopkg.in/yaml.v3"
+	"strings"
+	"testing"
+)
+
+func TestBoolValueString(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{"true", "true"},
+		{"false", "false"},
+		{"one", "one"},
+		{"", ""},
+		{" ", " "},
+		{"off", "false"},
+		{"on", "true"},
+	}
+
+	for _, tt := range tests {
+		t.Run(string(tt.input), func(t *testing.T) {
+			bv, _ := NewBoolValue(tt.input)
+
+			result := bv.String()
+			if result != tt.expected {
+				t.Errorf("result = %s; expected %s", result, tt.expected)
+			}
+		})
+	}
+}
+func TestBoolValueRaw(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{"true", "true"},
+		{"false", "false"},
+		{"one", "one"},
+		{"", ""},
+		{" ", " "},
+		{"off", "off"},
+		{"on", "on"},
+	}
+
+	for _, tt := range tests {
+		t.Run(string(tt.input), func(t *testing.T) {
+			bv, _ := NewBoolValue(tt.input)
+
+			result := bv.Raw()
+			if result != tt.expected {
+				t.Errorf("result = %s; expected %s", result, tt.expected)
+			}
+		})
+	}
+}
+
+func TestBoolValueIsTrue(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected bool
+	}{
+		{"true", true},
+		{"false", false},
+		{"one", false},
+		{"", false},
+		{" ", false},
+		{"gurke", false},
+		{"off", false},
+		{"on", true},
+		{"no", false},
+		{"yes", true},
+		{"0", false},
+		{"1", true},
+	}
+
+	for _, tt := range tests {
+		t.Run(string(tt.input), func(t *testing.T) {
+			bv, _ := NewBoolValue(tt.input)
+
+			result := bv.IsTrue()
+			if result != tt.expected {
+				t.Errorf("result = %t; expected %t", result, tt.expected)
+			}
+		})
+	}
+}
+
+// TestBoolValueYAMLSerialization tests the YAML serialization and deserialization of BoolValue
+func TestBoolValueYAMLMarshaling(t *testing.T) {
+	cases := []struct {
+		name       string
+		boolValue  BoolValue
+		expected   string
+		shouldFail bool
+	}{
+		{
+			name:      "True value",
+			boolValue: BoolValue{value: true, valid: true, raw: "true"},
+			expected:  "true",
+		},
+		{
+			name:      "False value",
+			boolValue: BoolValue{value: false, valid: true, raw: "false"},
+			expected:  "false",
+		},
+		{
+			name:      "Invalid value",
+			boolValue: BoolValue{valid: false, raw: "maybe"},
+			expected:  "false",
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			data, err := yaml.Marshal(&c.boolValue)
+			if err != nil {
+				if !c.shouldFail {
+					t.Errorf("Marshaling failed when it should not have: %v", err)
+				}
+				return
+			}
+
+			if strings.TrimSpace(string(data)) != c.expected {
+				t.Errorf("Expected %s but got %s", c.expected, string(data))
+			}
+		})
+	}
+}
+
+func TestBoolValueYAMLUnmarshaling(t *testing.T) {
+	cases := []struct {
+		name        string
+		yamlContent string
+		expected    string
+		shouldFail  bool
+	}{
+		{
+			name:        "True value",
+			yamlContent: `true`,
+			expected:    "true",
+		},
+		{
+			name:        "False value",
+			yamlContent: `false`,
+			expected:    "false",
+		},
+		{
+			name:        "Invalid value",
+			yamlContent: `maybe`,
+			shouldFail:  true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+
+			var unmarshal BoolValue
+			data := []byte(c.yamlContent)
+			err := yaml.Unmarshal(data, &unmarshal)
+			if err != nil {
+				if !c.shouldFail {
+					t.Errorf("Unmarshaling failed when it should not have: %v", err)
+				}
+				return
+			}
+			if unmarshal.String() != c.expected {
+				t.Errorf("Unmarshaled value %v does not match original %v", unmarshal, c.expected)
+			}
+		})
+	}
+}
+
+type BoolValueTestStruct struct {
+	Value BoolValue
+}
+
+func TestBoolValueJsonDecode(t *testing.T) {
+
+	cases := []struct {
+		name        string
+		jsonContent string
+		expected    string
+		shouldFail  bool
+	}{
+		{
+			name:        "True value",
+			jsonContent: "{\"value\":\"true\"}",
+			expected:    "true",
+		},
+		{
+			name:        "False value",
+			jsonContent: "{\"value\":\"false\"}",
+			expected:    "false",
+		},
+		{
+			name:        "Invalid value",
+			jsonContent: "{\"value\":\"gurke\"}",
+			shouldFail:  true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+
+			var unmarshal BoolValueTestStruct
+			data := []byte(c.jsonContent)
+			err := json.Unmarshal(data, &unmarshal)
+			if err != nil {
+				if !c.shouldFail {
+					t.Errorf("Unmarshaling failed when it should not have: %v", err)
+				}
+				return
+			}
+			if unmarshal.Value.String() != c.expected {
+				t.Errorf("Unmarshaled value %v does not match original %v", unmarshal, c.expected)
+			}
+		})
+	}
+}
+
+func TestBoolValueYamlDecode(t *testing.T) {
+
+	cases := []struct {
+		name        string
+		yamlContent string
+		expected    string
+		shouldFail  bool
+	}{
+		{
+			name: "True value",
+			yamlContent: `
+value: true
+`,
+			expected: "true",
+		},
+		{
+			name: "False value",
+			yamlContent: `
+value: false
+`,
+			expected: "false",
+		},
+		{
+			name: "Invalid value",
+			yamlContent: `
+value: gurke
+`,
+			shouldFail: true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+
+			var unmarshal BoolValueTestStruct
+			data := []byte(c.yamlContent)
+			err := yaml.Unmarshal(data, &unmarshal)
+			if err != nil {
+				if !c.shouldFail {
+					t.Errorf("Unmarshaling failed when it should not have: %v", err)
+				}
+				return
+			}
+			if unmarshal.Value.String() != c.expected {
+				t.Errorf("Unmarshaled value %v does not match original %v", unmarshal, c.expected)
+			}
+		})
+	}
+}
diff --git a/value-path.go b/value-path.go
new file mode 100644
index 0000000..1e33da3
--- /dev/null
+++ b/value-path.go
@@ -0,0 +1,55 @@
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+package configuration
+
+import "path"
+
+// deprecated
+type PathValueInterface interface {
+	String() string
+	Dir() string
+	Ext() string
+	Base() string
+}
+
+// PathValuer is an interface for path values
+type PathValuer interface {
+	String() string
+	Dir() string
+	Ext() string
+	Base() string
+	IsAbs() bool
+	Join(parts ...string) PathValue
+	Clean() PathValue
+}
+
+type PathValue string
+
+func (p PathValue) Dir() string {
+	return path.Dir(string(p))
+}
+
+func (p PathValue) Ext() string {
+	return path.Ext(string(p))
+}
+
+func (p PathValue) String() string {
+	return string(p)
+}
+
+func (p PathValue) Base() string {
+	return path.Base(string(p))
+}
+
+func (p PathValue) IsAbs() bool {
+	return path.IsAbs(string(p))
+}
+
+func (p PathValue) Clean() PathValue {
+	return PathValue(path.Clean(string(p)))
+}
+
+func (p PathValue) Join(parts ...string) PathValue {
+	return PathValue(path.Join(append([]string{string(p)}, parts...)...))
+}
diff --git a/path-value_test.go b/value-path_test.go
similarity index 100%
rename from path-value_test.go
rename to value-path_test.go
-- 
GitLab