Skip to content

Commit 1f56d77

Browse files
committed
feature: add AdditionalProperties to schemaRoot
Signed-off-by: Aleksandar Stojanov <me@fnd.works>
1 parent 27e75c2 commit 1f56d77

File tree

7 files changed

+226
-4
lines changed

7 files changed

+226
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ usage: helm schema [-input STR] [-draft INT] [-output STR]
9898
Multiple yaml files as inputs (comma-separated)
9999
-output string
100100
Output file path (default "values.schema.json")
101+
-schemaRoot.additionalProperties value
102+
JSON schema additional properties (true/false)
101103
-schemaRoot.description string
102104
JSON schema description
103105
-schemaRoot.id string

pkg/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func ParseFlags(progname string, args []string) (config *Config, output string,
2222
flags.StringVar(&conf.SchemaRoot.ID, "schemaRoot.id", "", "JSON schema ID")
2323
flags.StringVar(&conf.SchemaRoot.Title, "schemaRoot.title", "", "JSON schema title")
2424
flags.StringVar(&conf.SchemaRoot.Description, "schemaRoot.description", "", "JSON schema description")
25+
flags.Var(&conf.SchemaRoot.AdditionalProperties, "schemaRoot.additionalProperties", "JSON schema additional properties (true/false)")
2526

2627
err = flags.Parse(args)
2728
if err != nil {

pkg/cmd_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func TestParseFlagsFail(t *testing.T) {
7171
{[]string{"-input"}, "flag needs an argument"},
7272
{[]string{"-draft", "foo"}, "invalid value"},
7373
{[]string{"-foo"}, "flag provided but not defined"},
74+
{[]string{"-schemaRoot.additionalProperties", "null"}, "invalid boolean value"},
7475
}
7576

7677
for _, tt := range tests {

pkg/generator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func GenerateJsonSchema(config *Config) error {
8282
}
8383
jsonSchemaMap["$schema"] = schemaURL // Include the schema draft version
8484

85+
if config.SchemaRoot.AdditionalProperties.IsSet() {
86+
jsonSchemaMap["additionalProperties"] = config.SchemaRoot.AdditionalProperties.Value()
87+
}
88+
8589
// If validation is successful, marshal the schema and save to the file
8690
jsonBytes, err := json.MarshalIndent(jsonSchemaMap, "", indentString)
8791
if err != nil {

pkg/generator_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pkg
33
import (
44
"encoding/json"
55
"errors"
6+
"fmt"
67
"os"
78
"testing"
89

@@ -132,3 +133,72 @@ func TestGenerateJsonSchema_Errors(t *testing.T) {
132133
})
133134
}
134135
}
136+
func TestGenerateJsonSchema_AdditionalProperties(t *testing.T) {
137+
tests := []struct {
138+
name string
139+
additionalPropertiesSet bool
140+
additionalProperties bool
141+
expected interface{}
142+
}{
143+
{
144+
name: "AdditionalProperties set to true",
145+
additionalPropertiesSet: true,
146+
additionalProperties: true,
147+
expected: true,
148+
},
149+
{
150+
name: "AdditionalProperties set to false",
151+
additionalPropertiesSet: true,
152+
additionalProperties: false,
153+
expected: false,
154+
},
155+
{
156+
name: "AdditionalProperties not set",
157+
additionalPropertiesSet: false,
158+
expected: nil,
159+
},
160+
}
161+
162+
for _, tt := range tests {
163+
t.Run(tt.name, func(t *testing.T) {
164+
additionalPropertiesFlag := &BoolFlag{}
165+
if tt.additionalPropertiesSet {
166+
if err := additionalPropertiesFlag.Set(fmt.Sprintf("%t", tt.additionalProperties)); err != nil {
167+
t.Fatalf("Failed to set additionalPropertiesFlag: %v", err)
168+
}
169+
}
170+
171+
config := &Config{
172+
input: []string{"../testdata/empty.yaml"},
173+
outputPath: "../testdata/empty.schema.json",
174+
draft: 2020,
175+
indent: 4,
176+
SchemaRoot: SchemaRoot{
177+
ID: "",
178+
Title: "",
179+
Description: "",
180+
AdditionalProperties: *additionalPropertiesFlag,
181+
},
182+
}
183+
184+
err := GenerateJsonSchema(config)
185+
assert.NoError(t, err)
186+
187+
generatedBytes, err := os.ReadFile(config.outputPath)
188+
assert.NoError(t, err)
189+
190+
var generatedSchema map[string]interface{}
191+
err = json.Unmarshal(generatedBytes, &generatedSchema)
192+
assert.NoError(t, err)
193+
194+
if tt.expected == nil {
195+
_, exists := generatedSchema["additionalProperties"]
196+
assert.False(t, exists, "additionalProperties should not be present in the generated schema")
197+
} else {
198+
assert.Equal(t, tt.expected, generatedSchema["additionalProperties"], "additionalProperties value mismatch")
199+
}
200+
201+
os.Remove(config.outputPath)
202+
})
203+
}
204+
}

pkg/utils.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package pkg
22

3-
import "strings"
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
48

59
// SchemaRoot struct defines root object of schema
610
type SchemaRoot struct {
7-
ID string
8-
Title string
9-
Description string
11+
ID string
12+
Title string
13+
Description string
14+
AdditionalProperties BoolFlag
1015
}
1116

1217
// Save values of parsed flags in Config
@@ -36,6 +41,41 @@ func (m *multiStringFlag) Set(value string) error {
3641
return nil
3742
}
3843

44+
// Custom BoolFlag type that tracks if it was explicitly set
45+
type BoolFlag struct {
46+
set bool
47+
value bool
48+
}
49+
50+
func (b *BoolFlag) String() string {
51+
if b.set {
52+
return fmt.Sprintf("%t", b.value)
53+
}
54+
return "not set"
55+
}
56+
57+
func (b *BoolFlag) Set(value string) error {
58+
if value == "true" {
59+
b.value = true
60+
} else if value == "false" {
61+
b.value = false
62+
} else {
63+
return errors.New("invalid boolean value")
64+
}
65+
b.set = true
66+
return nil
67+
}
68+
69+
// Accessor method to check if the flag was explicitly set
70+
func (b *BoolFlag) IsSet() bool {
71+
return b.set
72+
}
73+
74+
// Accessor method to get the value of the flag
75+
func (b *BoolFlag) Value() bool {
76+
return b.value
77+
}
78+
3979
func uniqueStringAppend(dest []string, src ...string) []string {
4080
existingItems := make(map[string]bool)
4181
for _, item := range dest {

pkg/utils_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkg
22

33
import (
4+
"errors"
45
"reflect"
56
"testing"
67
)
@@ -140,3 +141,106 @@ func TestUniqueStringAppend(t *testing.T) {
140141
})
141142
}
142143
}
144+
145+
func TestBoolFlag_String(t *testing.T) {
146+
tests := []struct {
147+
name string
148+
set bool
149+
value bool
150+
expected string
151+
}{
152+
{"Unset flag", false, false, "not set"},
153+
{"Set flag to true", true, true, "true"},
154+
{"Set flag to false", true, false, "false"},
155+
}
156+
157+
for _, tt := range tests {
158+
t.Run(tt.name, func(t *testing.T) {
159+
b := &BoolFlag{
160+
set: tt.set,
161+
value: tt.value,
162+
}
163+
if got := b.String(); got != tt.expected {
164+
t.Errorf("BoolFlag.String() = %v, want %v", got, tt.expected)
165+
}
166+
})
167+
}
168+
}
169+
170+
func TestBoolFlag_Set(t *testing.T) {
171+
tests := []struct {
172+
name string
173+
input string
174+
expectedSet bool
175+
expectedValue bool
176+
expectedErr error
177+
}{
178+
{"Set true", "true", true, true, nil},
179+
{"Set false", "false", true, false, nil},
180+
{"Set invalid", "invalid", false, false, errors.New("invalid boolean value")},
181+
}
182+
183+
for _, tt := range tests {
184+
t.Run(tt.name, func(t *testing.T) {
185+
b := &BoolFlag{}
186+
err := b.Set(tt.input)
187+
if (err != nil) != (tt.expectedErr != nil) {
188+
t.Errorf("BoolFlag.Set() error = %v, expectedErr %v", err, tt.expectedErr)
189+
return
190+
}
191+
if err != nil && err.Error() != tt.expectedErr.Error() {
192+
t.Errorf("BoolFlag.Set() error = %v, expectedErr %v", err, tt.expectedErr)
193+
}
194+
if b.set != tt.expectedSet {
195+
t.Errorf("BoolFlag.set = %v, expected %v", b.set, tt.expectedSet)
196+
}
197+
if b.value != tt.expectedValue {
198+
t.Errorf("BoolFlag.value = %v, expected %v", b.value, tt.expectedValue)
199+
}
200+
})
201+
}
202+
}
203+
204+
func TestBoolFlag_IsSet(t *testing.T) {
205+
tests := []struct {
206+
name string
207+
set bool
208+
expected bool
209+
}{
210+
{"Flag is not set", false, false},
211+
{"Flag is set", true, true},
212+
}
213+
214+
for _, tt := range tests {
215+
t.Run(tt.name, func(t *testing.T) {
216+
b := &BoolFlag{
217+
set: tt.set,
218+
}
219+
if got := b.IsSet(); got != tt.expected {
220+
t.Errorf("BoolFlag.IsSet() = %v, want %v", got, tt.expected)
221+
}
222+
})
223+
}
224+
}
225+
226+
func TestBoolFlag_GetValue(t *testing.T) {
227+
tests := []struct {
228+
name string
229+
value bool
230+
expected bool
231+
}{
232+
{"Flag value is false", false, false},
233+
{"Flag value is true", true, true},
234+
}
235+
236+
for _, tt := range tests {
237+
t.Run(tt.name, func(t *testing.T) {
238+
b := &BoolFlag{
239+
value: tt.value,
240+
}
241+
if got := b.Value(); got != tt.expected {
242+
t.Errorf("BoolFlag.GetValue() = %v, want %v", got, tt.expected)
243+
}
244+
})
245+
}
246+
}

0 commit comments

Comments
 (0)