Skip to content

Commit 929a433

Browse files
committed
Unify properties to map conversion code
In order to do validation against the JSON schema the native properties.Map format of the Arduino project configuration files must be converted to the map[string]interface{} type. This process is made more complex by the need to selectively recurse the data into multiple key levels. Previously, this was done in three different places in the code, each tailored to the specific needs of the configuration files it worked with, and using slightly different approaches. Unifying all that code into a single general purpose function will make the code easier to maintain.
1 parent ff95327 commit 929a433

File tree

6 files changed

+80
-58
lines changed

6 files changed

+80
-58
lines changed

internal/project/general/general.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,33 @@ import (
2121
)
2222

2323
/*
24-
PropertiesToFirstLevelExpandedMap converts properties.Map data structures to map[string]interface that maps between .
25-
Even though boards/properties.txt have a multi-level nested data structure, the format has the odd characteristic of
26-
allowing a key to be both an object and a string simultaneously, which is not compatible with Golang maps or JSON. So
27-
the data structure used is a map of the first level keys (necessary to accommodate the board/prograrmmer IDs) to the
28-
full remainder of the keys (rather than recursing through each key level individually), to string values.
24+
PropertiesToMap converts properties.Map data structures to map[string]interface with the specified number of key levels.
25+
The Arduino project configuration fields have an odd usage of the properties.Map format. Dots may sometimes indicate
26+
nested keys, but in other cases they are merely a character in the key string. There are cases where a completely
27+
programmatic recursion of the properties into a fully nested structure would result in the impossibility of some keys
28+
having bot a string and a map type, which is not supported. For this reason, it's necessary to manually configure the
29+
recursion of key levels on a case-by-case basis.
30+
In the event a full recursion of key levels is desired, set the levels argument to a value <1.
2931
*/
30-
func PropertiesToFirstLevelExpandedMap(flatProperties *properties.Map) map[string]interface{} {
32+
func PropertiesToMap(flatProperties *properties.Map, levels int) map[string]interface{} {
3133
propertiesInterface := make(map[string]interface{})
32-
keys := flatProperties.FirstLevelKeys()
34+
35+
var keys []string
36+
if levels != 1 {
37+
keys = flatProperties.FirstLevelKeys()
38+
} else {
39+
keys = flatProperties.Keys()
40+
}
41+
3342
for _, key := range keys {
34-
subtreeMap := flatProperties.SubTree(key).AsMap()
35-
// This level also must be converted to map[string]interface{}.
36-
subtreeInterface := make(map[string]interface{})
37-
for subtreeKey, subtreeValue := range subtreeMap {
38-
subtreeInterface[subtreeKey] = subtreeValue
43+
subTree := flatProperties.SubTree(key)
44+
if subTree.Size() > 0 {
45+
// This key contains a map.
46+
propertiesInterface[key] = PropertiesToMap(subTree, levels-1)
47+
} else {
48+
// This key contains a string, no more recursion is possible.
49+
propertiesInterface[key] = flatProperties.Get(key)
3950
}
40-
propertiesInterface[key] = subtreeInterface
4151
}
4252

4353
return propertiesInterface

internal/project/general/general_test.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import (
2424
"github.com/stretchr/testify/require"
2525
)
2626

27-
func TestPropertiesToFirstLevelExpandedMap(t *testing.T) {
27+
func TestPropertiesToMap(t *testing.T) {
2828
rawProperties := []byte(`
29+
hello=world
30+
goodbye=
2931
foo.bar=asdf
3032
foo.baz=zxcv
3133
bar.bat.bam=123
@@ -34,6 +36,18 @@ func TestPropertiesToFirstLevelExpandedMap(t *testing.T) {
3436
require.Nil(t, err)
3537

3638
expectedMapOutput := map[string]interface{}{
39+
"hello": "world",
40+
"goodbye": "",
41+
"foo.bar": "asdf",
42+
"foo.baz": "zxcv",
43+
"bar.bat.bam": "123",
44+
}
45+
46+
assert.True(t, reflect.DeepEqual(expectedMapOutput, PropertiesToMap(propertiesInput, 1)))
47+
48+
expectedMapOutput = map[string]interface{}{
49+
"hello": "world",
50+
"goodbye": "",
3751
"foo": map[string]interface{}{
3852
"bar": "asdf",
3953
"baz": "zxcv",
@@ -43,5 +57,22 @@ func TestPropertiesToFirstLevelExpandedMap(t *testing.T) {
4357
},
4458
}
4559

46-
assert.True(t, reflect.DeepEqual(expectedMapOutput, PropertiesToFirstLevelExpandedMap(propertiesInput)))
60+
assert.True(t, reflect.DeepEqual(expectedMapOutput, PropertiesToMap(propertiesInput, 2)))
61+
62+
expectedMapOutput = map[string]interface{}{
63+
"hello": "world",
64+
"goodbye": "",
65+
"foo": map[string]interface{}{
66+
"bar": "asdf",
67+
"baz": "zxcv",
68+
},
69+
"bar": map[string]interface{}{
70+
"bat": map[string]interface{}{
71+
"bam": "123",
72+
},
73+
},
74+
}
75+
76+
assert.True(t, reflect.DeepEqual(expectedMapOutput, PropertiesToMap(propertiesInput, 3)))
77+
assert.True(t, reflect.DeepEqual(expectedMapOutput, PropertiesToMap(propertiesInput, 0)))
4778
}

internal/project/library/libraryproperties/libraryproperties.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package libraryproperties
1818

1919
import (
20+
"github.com/arduino/arduino-lint/internal/project/general"
2021
"github.com/arduino/arduino-lint/internal/rule/schema"
2122
"github.com/arduino/arduino-lint/internal/rule/schema/compliancelevel"
2223
"github.com/arduino/arduino-lint/internal/rule/schema/schemadata"
@@ -48,11 +49,7 @@ func Validate(libraryProperties *properties.Map) map[compliancelevel.Type]schema
4849

4950
// Convert the library.properties data from the native properties.Map type to the interface type required by the schema
5051
// validation package.
51-
libraryPropertiesMap := libraryProperties.AsMap()
52-
libraryPropertiesInterface := make(map[string]interface{}, len(libraryPropertiesMap))
53-
for k, v := range libraryPropertiesMap {
54-
libraryPropertiesInterface[k] = v
55-
}
52+
libraryPropertiesInterface := general.PropertiesToMap(libraryProperties, 1)
5653

5754
validationResults[compliancelevel.Permissive] = schema.Validate(libraryPropertiesInterface, schemaObject[compliancelevel.Permissive])
5855
validationResults[compliancelevel.Specification] = schema.Validate(libraryPropertiesInterface, schemaObject[compliancelevel.Specification])

internal/project/platform/boardstxt/boardstxt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func Validate(boardsTxt *properties.Map) map[compliancelevel.Type]schema.Validat
5151
}
5252

5353
//Convert the boards.txt data from the native properties.Map type to the interface type required by the schema validation package.
54-
boardsTxtInterface := general.PropertiesToFirstLevelExpandedMap(boardsTxt)
54+
boardsTxtInterface := general.PropertiesToMap(boardsTxt, 2)
5555

5656
validationResults[compliancelevel.Permissive] = schema.Validate(boardsTxtInterface, schemaObject[compliancelevel.Permissive])
5757
validationResults[compliancelevel.Specification] = schema.Validate(boardsTxtInterface, schemaObject[compliancelevel.Specification])

internal/project/platform/programmerstxt/programmerstxt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func Validate(programmersTxt *properties.Map) map[compliancelevel.Type]schema.Va
5050
}
5151

5252
//Convert the programmers.txt data from the native properties.Map type to the interface type required by the schema validation package.
53-
programmersTxtInterface := general.PropertiesToFirstLevelExpandedMap(programmersTxt)
53+
programmersTxtInterface := general.PropertiesToMap(programmersTxt, 2)
5454

5555
validationResults[compliancelevel.Permissive] = schema.Validate(programmersTxtInterface, schemaObject[compliancelevel.Permissive])
5656
validationResults[compliancelevel.Specification] = schema.Validate(programmersTxtInterface, schemaObject[compliancelevel.Specification])

internal/rule/schema/schema_test.go

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"regexp"
2020
"testing"
2121

22+
"github.com/arduino/arduino-lint/internal/project/general"
2223
"github.com/arduino/arduino-lint/internal/rule/schema/testdata"
2324
"github.com/arduino/go-properties-orderedmap"
2425
"github.com/ory/jsonschema/v3"
@@ -46,23 +47,6 @@ func init() {
4647
)
4748
}
4849

49-
// propertiesToMap converts properties.Map data structures to map[string]interface.
50-
func propertiesToMap(propertiesInput *properties.Map) map[string]interface{} {
51-
mapOutput := make(map[string]interface{})
52-
keys := propertiesInput.FirstLevelKeys()
53-
for _, key := range keys {
54-
subTree := propertiesInput.SubTree(key)
55-
if subTree.Size() > 0 {
56-
// This key contains a map, recurse it.
57-
mapOutput[key] = propertiesToMap(subTree)
58-
} else {
59-
// This key contains a string, no more recursion is possible.
60-
mapOutput[key] = propertiesInput.Get(key)
61-
}
62-
}
63-
return mapOutput
64-
}
65-
6650
func TestCompile(t *testing.T) {
6751
require.Panics(t, func() {
6852
Compile("valid-schema-with-references.json", []string{"nonexistent.json"}, testdata.Asset)
@@ -99,87 +83,87 @@ func TestCompile(t *testing.T) {
9983
func TestValidate(t *testing.T) {
10084
schemaObject := Compile("valid-schema.json", []string{}, testdata.Asset)
10185
propertiesMap := properties.NewFromHashmap(validMap)
102-
validationResult := Validate(propertiesToMap(propertiesMap), schemaObject)
86+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), schemaObject)
10387
require.Nil(t, validationResult.Result)
10488

105-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
89+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
10690
require.Nil(t, validationResult.Result)
10791

10892
propertiesMap.Set("property1", "a")
109-
validationResult = Validate(propertiesToMap(propertiesMap), schemaObject)
93+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), schemaObject)
11094
require.Equal(t, "#/property1", validationResult.Result.InstancePtr)
11195
require.Equal(t, "#/properties/property1/minLength", validationResult.Result.SchemaPtr)
11296
}
11397

11498
func TestRequiredPropertyMissing(t *testing.T) {
11599
propertiesMap := properties.NewFromHashmap(validMap)
116-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
100+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
117101
require.False(t, RequiredPropertyMissing("property1", validationResult))
118102

119103
propertiesMap.Remove("property1")
120-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
104+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
121105
require.True(t, RequiredPropertyMissing("property1", validationResult))
122106
}
123107

124108
func TestPropertyPatternMismatch(t *testing.T) {
125109
propertiesMap := properties.NewFromHashmap(validMap)
126-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
110+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
127111
require.False(t, PropertyPatternMismatch("property2", validationResult))
128112

129113
propertiesMap.Set("property2", "fOo")
130-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
114+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
131115
require.True(t, PropertyPatternMismatch("property2", validationResult))
132116

133117
require.False(t, PropertyPatternMismatch("property1", validationResult))
134118
}
135119

136120
func TestPropertyLessThanMinLength(t *testing.T) {
137121
propertiesMap := properties.NewFromHashmap(validMap)
138-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
122+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
139123
require.False(t, PropertyLessThanMinLength("property1", validationResult))
140124

141125
propertiesMap.Set("property1", "a")
142-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
126+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
143127
require.True(t, PropertyLessThanMinLength("property1", validationResult))
144128
}
145129

146130
func TestPropertyGreaterThanMaxLength(t *testing.T) {
147131
propertiesMap := properties.NewFromHashmap(validMap)
148-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
132+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
149133
require.False(t, PropertyGreaterThanMaxLength("property1", validationResult))
150134

151135
propertiesMap.Set("property1", "12345")
152-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
136+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
153137
require.True(t, PropertyGreaterThanMaxLength("property1", validationResult))
154138
}
155139

156140
func TestPropertyEnumMismatch(t *testing.T) {
157141
propertiesMap := properties.NewFromHashmap(validMap)
158-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
142+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
159143
require.False(t, PropertyEnumMismatch("property3", validationResult))
160144

161145
propertiesMap.Set("property3", "invalid")
162-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
146+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
163147
require.True(t, PropertyEnumMismatch("property3", validationResult))
164148
}
165149

166150
func TestMisspelledOptionalPropertyFound(t *testing.T) {
167151
propertiesMap := properties.NewFromHashmap(validMap)
168-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
152+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
169153
require.False(t, MisspelledOptionalPropertyFound(validationResult))
170154

171155
propertiesMap.Set("porperties", "foo")
172-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
156+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
173157
require.True(t, MisspelledOptionalPropertyFound(validationResult))
174158
}
175159

176160
func TestValidationErrorMatch(t *testing.T) {
177161
propertiesMap := properties.NewFromHashmap(validMap)
178-
validationResult := Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
162+
validationResult := Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
179163
require.False(t, ValidationErrorMatch("", "", "", "", validationResult))
180164

181165
propertiesMap.Set("property2", "fOo")
182-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
166+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
183167
require.False(t, ValidationErrorMatch("nomatch", "nomatch", "nomatch", "nomatch", validationResult))
184168
require.False(t, ValidationErrorMatch("^#/property2$", "nomatch", "nomatch", "nomatch", validationResult))
185169
require.False(t, ValidationErrorMatch("^#/property2$", "/pattern$", "nomatch", "nomatch", validationResult))
@@ -188,12 +172,12 @@ func TestValidationErrorMatch(t *testing.T) {
188172
require.True(t, ValidationErrorMatch("", "", "", "", validationResult))
189173

190174
propertiesMap.Set("property3", "bAz")
191-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
175+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
192176
require.True(t, ValidationErrorMatch("^#/property3$", "/pattern$", "", "", validationResult), "Match pointer below logic inversion keyword")
193177

194178
propertiesMap = properties.NewFromHashmap(validMap)
195179
propertiesMap.Remove("property1")
196-
validationResult = Validate(propertiesToMap(propertiesMap), validSchemaWithReferences)
180+
validationResult = Validate(general.PropertiesToMap(propertiesMap, 0), validSchemaWithReferences)
197181
require.False(t, ValidationErrorMatch("nomatch", "nomatch", "nomatch", "nomatch", validationResult))
198182
require.True(t, ValidationErrorMatch("", "", "", "^#/property1$", validationResult))
199183
}

0 commit comments

Comments
 (0)