@@ -21,6 +21,7 @@ import (
2121 "path/filepath"
2222 "reflect"
2323 "regexp"
24+ "slices"
2425 "strconv"
2526 "strings"
2627 "time"
@@ -70,11 +71,15 @@ const (
7071// layerExtensions keeps a map of registered extensions.
7172var layerExtensions = map [string ]LayerSectionExtension {}
7273
74+ // layerBuiltins represents all the built-in layer sections. This list is used
75+ // for identifying built-in fields in this package. It is unit tested to match
76+ // the YAML fields exposed in the Layer type, to catch inconsistencies.
77+ var layerBuiltins = []string {"summary" , "description" , "services" , "checks" , "log-targets" }
78+
7379// RegisterExtension adds a plan schema extension. All registrations must be
7480// done before the plan library is used.
7581func RegisterExtension (field string , ext LayerSectionExtension ) {
76- switch field {
77- case "summary" , "description" , "services" , "checks" , "log-targets" :
82+ if slices .Contains (layerBuiltins , field ) {
7883 panic (fmt .Sprintf ("internal error: extension %q already used as built-in field" , field ))
7984 }
8085 if _ , ok := layerExtensions [field ]; ok {
@@ -1226,6 +1231,11 @@ func ParseLayer(order int, label string, data []byte) (*Layer, error) {
12261231 "checks" : & layer .Checks ,
12271232 "log-targets" : & layer .LogTargets ,
12281233 }
1234+ // Make sure builtinSections contains the exact same fields as expected
1235+ // in the Layer type.
1236+ if ! mapHasKeys (builtinSections , layerBuiltins ) {
1237+ panic ("internal error: parsed fields and layer fields differ" )
1238+ }
12291239
12301240 layerSections := make (map [string ]yaml.Node )
12311241 // Deliberately pre-allocate at least an empty yaml.Node for every
@@ -1245,7 +1255,7 @@ func ParseLayer(order int, label string, data []byte) (*Layer, error) {
12451255 }
12461256
12471257 for field , section := range layerSections {
1248- if _ , builtin := builtinSections [ field ]; builtin {
1258+ if slices . Contains ( layerBuiltins , field ) {
12491259 // The following issue prevents us from using the yaml.Node decoder
12501260 // with KnownFields = true behaviour. Once one of the proposals get
12511261 // merged, we can remove the intermediate Marshal step.
@@ -1312,6 +1322,20 @@ func ParseLayer(order int, label string, data []byte) (*Layer, error) {
13121322 return layer , err
13131323}
13141324
1325+ // mapHasKeys returns true if the key list supplied is an exact match of the
1326+ // keys in the map (ordering is ignored).
1327+ func mapHasKeys [M ~ map [K ]V , K comparable , V any ](inMap M , keyList []K ) bool {
1328+ if len (inMap ) != len (keyList ) {
1329+ return false
1330+ }
1331+ for _ , key := range keyList {
1332+ if _ , ok := inMap [key ]; ! ok {
1333+ return false
1334+ }
1335+ }
1336+ return true
1337+ }
1338+
13151339func validServiceAction (action ServiceAction , additionalValid ... ServiceAction ) bool {
13161340 for _ , v := range additionalValid {
13171341 if action == v {
0 commit comments