Skip to content

Commit 3bea2c9

Browse files
authored
fix: handle shorthand_field defaults in skip-defaults dump (#357)
* fix: handle shorthand_field defaults in skip-defaults dump * tests: added unit tests for shorthand fields handling
1 parent e412658 commit 3bea2c9

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed

pkg/dump/skip_defaults.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,59 @@ func parseSchemaForDefaults(schema gjson.Result, defaultFields map[string]interf
431431
return true
432432
})
433433

434+
// Handle shorthand_fields by finding defaults from their "replaced_with" or "translate_backwards" paths
435+
shorthandFields := schema.Get("shorthand_fields")
436+
if shorthandFields.Exists() && shorthandFields.IsArray() {
437+
shorthandFields.ForEach(func(_, value gjson.Result) bool {
438+
ms := value.Map()
439+
for fieldName := range ms {
440+
fieldSchema := value.Get(fieldName)
441+
replacements := fieldSchema.Get("deprecation.replaced_with.#.path").Array()
442+
var replacedPaths []string
443+
var pathArray []gjson.Result
444+
445+
if len(replacements) > 0 {
446+
// replacements is an array of objects with path arrays inside each object.
447+
// Eg; {
448+
// "redis_host":
449+
// "replaced_with": [
450+
// {
451+
// "path": ["redis", "host"]
452+
// }
453+
// ]
454+
// }
455+
// typically there's only one; if multiple, we consider only the first one
456+
pathArray = replacements[0].Array()
457+
} else {
458+
// backward translation gives an array with the path segments
459+
// Eg; {
460+
// "redis_host": {
461+
// "translate_backwards": ["redis", "host"]
462+
// }
463+
// }
464+
backwardTranslation := fieldSchema.Get("translate_backwards")
465+
if backwardTranslation.Exists() {
466+
pathArray = backwardTranslation.Array()
467+
}
468+
}
469+
470+
replacedPaths = make([]string, len(pathArray))
471+
for i, segment := range pathArray {
472+
replacedPaths[i] = segment.String()
473+
}
474+
475+
if len(replacedPaths) > 0 {
476+
defaultValue := findDefaultInReplacementPath(schema, replacedPaths)
477+
if defaultValue.Exists() {
478+
defaultFields[fieldName] = defaultValue.Value()
479+
}
480+
}
481+
482+
}
483+
return true
484+
})
485+
}
486+
434487
// All credentials' schemas in konnect are embedded under "value" field
435488
// which doesn't match gateway schema or internal go-kong representation
436489
// Thus, merging values from "value" field to the defaultFields map directly
@@ -444,6 +497,64 @@ func parseSchemaForDefaults(schema gjson.Result, defaultFields map[string]interf
444497
return defaultFields
445498
}
446499

500+
// findDefaultInReplacementPath traverses the schema to find the default value at the specified path
501+
func findDefaultInReplacementPath(schema gjson.Result, pathSegments []string) gjson.Result {
502+
schemaFields := schema.Get("fields")
503+
if schemaFields.Type == gjson.Null {
504+
schemaFields = schema.Get("properties")
505+
}
506+
507+
// Begin iteration from the root schema fields
508+
// current represents the current level in the schema traversal
509+
// when the path is fully traversed, current will hold the default value
510+
// On each iteration, we update current to the next level in the schema, whenever
511+
// the path segment is found.
512+
current := schemaFields
513+
514+
for i, segment := range pathSegments {
515+
var next gjson.Result
516+
isLastSegment := i == len(pathSegments)-1
517+
isArray := current.IsArray()
518+
519+
current.ForEach(func(key, value gjson.Result) bool {
520+
var fieldExists bool
521+
var fieldValue gjson.Result
522+
523+
if isArray {
524+
// For arrays, check if the segment exists in the value
525+
fieldExists = value.Get(segment).Exists()
526+
fieldValue = value
527+
} else {
528+
// For objects, check if the key matches the segment
529+
fieldExists = key.String() == segment
530+
fieldValue = current
531+
}
532+
533+
if fieldExists {
534+
if isLastSegment {
535+
next = fieldValue.Get(segment + ".default")
536+
} else {
537+
next = fieldValue.Get(segment + ".fields")
538+
if next.Type == gjson.Null {
539+
next = fieldValue.Get(segment + ".properties")
540+
}
541+
}
542+
// stop iteration by returning false as we have found the field
543+
return false
544+
}
545+
return true
546+
})
547+
548+
if !next.Exists() {
549+
return gjson.Result{} // Return empty result if path not found
550+
}
551+
552+
current = next
553+
}
554+
555+
return current
556+
}
557+
447558
func stripDefaultValuesFromEntity(entity reflect.Value, defaultFields map[string]interface{}) error {
448559
if entity.Kind() != reflect.Struct {
449560
return fmt.Errorf("entity is not a struct")

pkg/dump/skip_defaults_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,180 @@ func TestParseSchemaForDefaults(t *testing.T) {
186186
}`,
187187
expectedFields: map[string]interface{}{},
188188
},
189+
{
190+
name: "schema with deprecated shorthand_fields with backward translation",
191+
schemaJSON: `{
192+
"fields": [
193+
{
194+
"name": {
195+
"type": "string",
196+
"default": "default-name"
197+
}
198+
},
199+
{
200+
"config": {
201+
"type": "record",
202+
"fields": [
203+
{
204+
"redis": {
205+
"type": "record",
206+
"fields": [
207+
{
208+
"host": {
209+
"type": "string",
210+
"default": "localhost"
211+
}
212+
},
213+
{
214+
"port": {
215+
"type": "number",
216+
"default": 6379
217+
}
218+
}
219+
]
220+
}
221+
}
222+
],
223+
"shorthand_fields": [
224+
{
225+
"redis_host": {
226+
"translate_backwards": [
227+
"redis",
228+
"host"
229+
],
230+
"type": "string"
231+
}
232+
},
233+
{
234+
"redis_port": {
235+
"translate_backwards": [
236+
"redis",
237+
"port"
238+
],
239+
"type": "integer"
240+
}
241+
}
242+
]
243+
}
244+
}
245+
]
246+
}`,
247+
expectedFields: map[string]interface{}{
248+
"name": "default-name",
249+
"config": map[string]interface{}{
250+
"redis": map[string]interface{}{
251+
"host": "localhost",
252+
"port": float64(6379),
253+
},
254+
"redis_host": "localhost",
255+
"redis_port": float64(6379),
256+
},
257+
},
258+
},
259+
{
260+
name: "schema with deprecatedshorthand_fields with replaced_with paths",
261+
schemaJSON: `{
262+
"fields": [
263+
{
264+
"name": {
265+
"type": "string",
266+
"default": "default-name"
267+
}
268+
},
269+
{
270+
"config": {
271+
"type": "record",
272+
"fields": [
273+
{
274+
"redis": {
275+
"type": "record",
276+
"fields": [
277+
{
278+
"host": {
279+
"type": "string",
280+
"default": "localhost"
281+
}
282+
},
283+
{
284+
"port": {
285+
"type": "number",
286+
"default": 6379
287+
}
288+
},
289+
{
290+
"read_timeout": {
291+
"type": "number",
292+
"default": 10
293+
}
294+
},
295+
{
296+
"send_timeout": {
297+
"type": "number",
298+
"default": 10
299+
}
300+
}
301+
]
302+
}
303+
}
304+
],
305+
"shorthand_fields": [
306+
{
307+
"redis_host": {
308+
"deprecation": {
309+
"replaced_with": [{
310+
"path": ["redis", "host"]
311+
}]
312+
},
313+
"type": "string"
314+
}
315+
},
316+
{
317+
"redis_port": {
318+
"deprecation": {
319+
"replaced_with": [
320+
{
321+
"path": ["redis", "port"]
322+
}
323+
]
324+
},
325+
"type": "integer"
326+
}
327+
},
328+
{
329+
"timeout": {
330+
"deprecation": {
331+
"replaced_with": [
332+
{
333+
"path": ["redis", "read_timeout"]
334+
},
335+
{
336+
"path": ["redis", "send_timeout"]
337+
}
338+
]
339+
},
340+
"type": "integer"
341+
}
342+
}
343+
]
344+
}
345+
}
346+
]
347+
}`,
348+
expectedFields: map[string]interface{}{
349+
"name": "default-name",
350+
"config": map[string]interface{}{
351+
"redis": map[string]interface{}{
352+
"host": "localhost",
353+
"port": float64(6379),
354+
"read_timeout": float64(10),
355+
"send_timeout": float64(10),
356+
},
357+
"redis_host": "localhost",
358+
"redis_port": float64(6379),
359+
"timeout": float64(10),
360+
},
361+
},
362+
},
189363
}
190364

191365
for _, tt := range tests {

0 commit comments

Comments
 (0)