Skip to content

Commit 09dbdd3

Browse files
committed
added integration test
On-behalf-of: @SAP [email protected] Signed-off-by: Artem Shcherbatiuk <[email protected]>
1 parent 5c7d414 commit 09dbdd3

File tree

5 files changed

+482
-54
lines changed

5 files changed

+482
-54
lines changed

gateway/resolver/dotted_keys.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ func processSpecToArrays(spec any) any {
8484
specMap[k] = mapToArray(v)
8585
} else if k == "selector" && v != nil {
8686
specMap[k] = processSelectorToArrays(v)
87+
} else if k == "template" && v != nil {
88+
specMap[k] = processTemplateToArrays(v)
8789
}
8890
}
8991
return spec
@@ -101,6 +103,8 @@ func processSpecToMaps(spec any) any {
101103
specMap[k] = arrayToMap(v)
102104
} else if k == "selector" && v != nil {
103105
specMap[k] = processSelectorToMaps(v)
106+
} else if k == "template" && v != nil {
107+
specMap[k] = processTemplateToMaps(v)
104108
}
105109
}
106110
return spec
@@ -136,6 +140,40 @@ func processSelectorToMaps(selector any) any {
136140
return selector
137141
}
138142

143+
// processTemplateToArrays handles spec.template.metadata and spec.template.spec conversion to arrays
144+
func processTemplateToArrays(template any) any {
145+
templateMap, ok := template.(map[string]interface{})
146+
if !ok {
147+
return template
148+
}
149+
150+
for k, v := range templateMap {
151+
if k == "metadata" && v != nil {
152+
templateMap[k] = processMetadataToArrays(v)
153+
} else if k == "spec" && v != nil {
154+
templateMap[k] = processSpecToArrays(v)
155+
}
156+
}
157+
return template
158+
}
159+
160+
// processTemplateToMaps handles spec.template.metadata and spec.template.spec conversion to maps
161+
func processTemplateToMaps(template any) any {
162+
templateMap, ok := template.(map[string]interface{})
163+
if !ok {
164+
return template
165+
}
166+
167+
for k, v := range templateMap {
168+
if k == "metadata" && v != nil {
169+
templateMap[k] = processMetadataToMaps(v)
170+
} else if k == "spec" && v != nil {
171+
templateMap[k] = processSpecToMaps(v)
172+
}
173+
}
174+
return template
175+
}
176+
139177
// mapToArray converts map[string]string to []Label
140178
func mapToArray(value any) any {
141179
valueMap, ok := value.(map[string]interface{})

gateway/schema/schema.go

Lines changed: 165 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -413,70 +413,181 @@ func (g *Gateway) convertSwaggerTypeToGraphQL(schema spec.Schema, typePrefix str
413413
}
414414

415415
func (g *Gateway) handleObjectFieldSpecType(fieldSpec spec.Schema, typePrefix string, fieldPath []string, processingTypes map[string]bool) (graphql.Output, graphql.Input, error) {
416+
// Handle object types with nested properties
416417
if len(fieldSpec.Properties) > 0 {
417-
typeName := g.generateTypeName(typePrefix, fieldPath)
418+
return g.handleObjectWithProperties(fieldSpec, typePrefix, fieldPath, processingTypes)
419+
}
418420

419-
// Check if type already generated
420-
if existingType, exists := g.typesCache[typeName]; exists {
421-
return existingType, g.inputTypesCache[typeName], nil
422-
}
421+
// Handle map types (map[string]string)
422+
if g.isStringMapType(fieldSpec) {
423+
return g.handleMapType(fieldPath, typePrefix)
424+
}
423425

424-
// Store placeholder to prevent recursion
425-
g.typesCache[typeName] = nil
426-
g.inputTypesCache[typeName] = nil
426+
// Fallback: empty object as JSON string
427+
return jsonStringScalar, jsonStringScalar, nil
428+
}
427429

428-
nestedFields, nestedInputFields, err := g.generateGraphQLFields(&fieldSpec, typeName, fieldPath, processingTypes)
429-
if err != nil {
430-
return nil, nil, err
431-
}
430+
// handleObjectWithProperties creates GraphQL types for objects with nested properties
431+
func (g *Gateway) handleObjectWithProperties(fieldSpec spec.Schema, typePrefix string, fieldPath []string, processingTypes map[string]bool) (graphql.Output, graphql.Input, error) {
432+
typeName := g.generateTypeName(typePrefix, fieldPath)
432433

433-
newType := graphql.NewObject(graphql.ObjectConfig{
434-
Name: sanitizeFieldName(typeName),
435-
Fields: nestedFields,
436-
})
434+
// Check if type already generated
435+
if existingType, exists := g.typesCache[typeName]; exists {
436+
return existingType, g.inputTypesCache[typeName], nil
437+
}
437438

438-
newInputType := graphql.NewInputObject(graphql.InputObjectConfig{
439-
Name: sanitizeFieldName(typeName) + "Input",
440-
Fields: nestedInputFields,
441-
})
439+
// Store placeholder to prevent recursion
440+
g.typesCache[typeName] = nil
441+
g.inputTypesCache[typeName] = nil
442442

443-
// Store the generated types
444-
g.typesCache[typeName] = newType
445-
g.inputTypesCache[typeName] = newInputType
446-
447-
return newType, newInputType, nil
448-
} else if fieldSpec.AdditionalProperties != nil && fieldSpec.AdditionalProperties.Schema != nil {
449-
// Handle map types
450-
if len(fieldSpec.AdditionalProperties.Schema.Type) == 1 && fieldSpec.AdditionalProperties.Schema.Type[0] == "string" {
451-
// Check if this is a field that should use Label arrays for dot-notation support
452-
currentFieldName := ""
453-
if len(fieldPath) > 0 {
454-
currentFieldName = fieldPath[len(fieldPath)-1]
455-
}
443+
nestedFields, nestedInputFields, err := g.generateGraphQLFields(&fieldSpec, typeName, fieldPath, processingTypes)
444+
if err != nil {
445+
return nil, nil, err
446+
}
456447

457-
// Check various contexts where Label arrays should be used
458-
isInMetadata := len(fieldPath) >= 2 && fieldPath[len(fieldPath)-2] == "metadata"
459-
isObjectMetaType := strings.Contains(typePrefix, "ObjectMeta") || strings.Contains(typePrefix, "meta_v1")
460-
isInSelector := len(fieldPath) >= 2 && fieldPath[len(fieldPath)-2] == "selector"
461-
isInSpec := len(fieldPath) >= 2 && fieldPath[len(fieldPath)-2] == "spec"
462-
463-
// Identify fields that need Label array treatment
464-
isLabelsOrAnnotations := (isInMetadata || isObjectMetaType) && (currentFieldName == "labels" || currentFieldName == "annotations")
465-
isNodeSelector := isInSpec && currentFieldName == "nodeSelector"
466-
isMatchLabels := isInSelector && currentFieldName == "matchLabels"
467-
468-
if isLabelsOrAnnotations || isNodeSelector || isMatchLabels {
469-
// Use List(LabelType) for output (allows querying key/value fields)
470-
// Use List(LabelInputType) for input (supports array syntax)
471-
return graphql.NewList(LabelType), graphql.NewList(LabelInputType), nil
472-
}
473-
// This is a regular map[string]string
474-
return stringMapScalar, stringMapScalar, nil
475-
}
448+
newType := graphql.NewObject(graphql.ObjectConfig{
449+
Name: sanitizeFieldName(typeName),
450+
Fields: nestedFields,
451+
})
452+
453+
newInputType := graphql.NewInputObject(graphql.InputObjectConfig{
454+
Name: sanitizeFieldName(typeName) + "Input",
455+
Fields: nestedInputFields,
456+
})
457+
458+
// Store the generated types
459+
g.typesCache[typeName] = newType
460+
g.inputTypesCache[typeName] = newInputType
461+
462+
return newType, newInputType, nil
463+
}
464+
465+
// handleMapType determines the appropriate GraphQL type for map[string]string fields
466+
func (g *Gateway) handleMapType(fieldPath []string, typePrefix string) (graphql.Output, graphql.Input, error) {
467+
if g.shouldUseLabelArrays(fieldPath, typePrefix) {
468+
// Use Label arrays for fields that can have dotted keys
469+
return graphql.NewList(LabelType), graphql.NewList(LabelInputType), nil
476470
}
477471

478-
// It's an empty object, serialize as JSON string
479-
return jsonStringScalar, jsonStringScalar, nil
472+
// Use regular string map scalar for normal map[string]string fields
473+
return stringMapScalar, stringMapScalar, nil
474+
}
475+
476+
// isStringMapType checks if the field spec represents a map[string]string
477+
func (g *Gateway) isStringMapType(fieldSpec spec.Schema) bool {
478+
if fieldSpec.AdditionalProperties == nil {
479+
return false
480+
}
481+
482+
if fieldSpec.AdditionalProperties.Schema == nil {
483+
return false
484+
}
485+
486+
if len(fieldSpec.AdditionalProperties.Schema.Type) != 1 {
487+
return false
488+
}
489+
490+
return fieldSpec.AdditionalProperties.Schema.Type[0] == "string"
491+
}
492+
493+
// shouldUseLabelArrays determines if a field needs Label array treatment for dotted keys
494+
func (g *Gateway) shouldUseLabelArrays(fieldPath []string, typePrefix string) bool {
495+
if len(fieldPath) == 0 {
496+
return false
497+
}
498+
499+
fieldName := fieldPath[len(fieldPath)-1]
500+
501+
if g.isLabelsField(fieldPath, typePrefix, fieldName) {
502+
return true
503+
}
504+
505+
if g.isAnnotationsField(fieldPath, typePrefix, fieldName) {
506+
return true
507+
}
508+
509+
if g.isNodeSelectorField(fieldPath, fieldName) {
510+
return true
511+
}
512+
513+
if g.isMatchLabelsField(fieldPath, fieldName) {
514+
return true
515+
}
516+
517+
return false
518+
}
519+
520+
// isLabelsField checks if this is a metadata.labels field
521+
func (g *Gateway) isLabelsField(fieldPath []string, typePrefix string, fieldName string) bool {
522+
if fieldName != "labels" {
523+
return false
524+
}
525+
526+
return g.isInMetadataContext(fieldPath, typePrefix)
527+
}
528+
529+
// isAnnotationsField checks if this is a metadata.annotations field
530+
func (g *Gateway) isAnnotationsField(fieldPath []string, typePrefix string, fieldName string) bool {
531+
if fieldName != "annotations" {
532+
return false
533+
}
534+
535+
return g.isInMetadataContext(fieldPath, typePrefix)
536+
}
537+
538+
// isNodeSelectorField checks if this is a spec.nodeSelector field
539+
func (g *Gateway) isNodeSelectorField(fieldPath []string, fieldName string) bool {
540+
if fieldName != "nodeSelector" {
541+
return false
542+
}
543+
544+
return g.isInSpecContext(fieldPath)
545+
}
546+
547+
// isMatchLabelsField checks if this is a selector.matchLabels field
548+
func (g *Gateway) isMatchLabelsField(fieldPath []string, fieldName string) bool {
549+
if fieldName != "matchLabels" {
550+
return false
551+
}
552+
553+
return g.isInSelectorContext(fieldPath)
554+
}
555+
556+
// isInMetadataContext checks if the field is within a metadata context
557+
func (g *Gateway) isInMetadataContext(fieldPath []string, typePrefix string) bool {
558+
// Check if we're directly in a metadata field
559+
if len(fieldPath) >= 2 && fieldPath[len(fieldPath)-2] == "metadata" {
560+
return true
561+
}
562+
563+
// Check if this is an ObjectMeta type
564+
if strings.Contains(typePrefix, "ObjectMeta") {
565+
return true
566+
}
567+
568+
if strings.Contains(typePrefix, "meta_v1") {
569+
return true
570+
}
571+
572+
return false
573+
}
574+
575+
// isInSpecContext checks if the field is within a spec context
576+
func (g *Gateway) isInSpecContext(fieldPath []string) bool {
577+
if len(fieldPath) < 2 {
578+
return false
579+
}
580+
581+
return fieldPath[len(fieldPath)-2] == "spec"
582+
}
583+
584+
// isInSelectorContext checks if the field is within a selector context
585+
func (g *Gateway) isInSelectorContext(fieldPath []string) bool {
586+
if len(fieldPath) < 2 {
587+
return false
588+
}
589+
590+
return fieldPath[len(fieldPath)-2] == "selector"
480591
}
481592

482593
func (g *Gateway) generateTypeName(typePrefix string, fieldPath []string) string {

0 commit comments

Comments
 (0)