@@ -34,20 +34,23 @@ type Gateway struct {
3434 inputTypesCache map [string ]* graphql.InputObject
3535 // Prevents naming conflict in case of the same Kind name in different groups/versions
3636 typeNameRegistry map [string ]string // map[Kind]GroupVersion
37+ // enhancedRefTypesCache stores enhanced reference types to prevent duplicate creation
38+ enhancedRefTypesCache map [string ]* graphql.Object
3739
3840 // categoryRegistry stores resources by category for typeByCategory query
3941 typeByCategory map [string ][]resolver.TypeByCategory
4042}
4143
4244func New (log * logger.Logger , definitions spec.Definitions , resolverProvider resolver.Provider ) (* Gateway , error ) {
4345 g := & Gateway {
44- log : log ,
45- resolver : resolverProvider ,
46- definitions : definitions ,
47- typesCache : make (map [string ]* graphql.Object ),
48- inputTypesCache : make (map [string ]* graphql.InputObject ),
49- typeNameRegistry : make (map [string ]string ),
50- typeByCategory : make (map [string ][]resolver.TypeByCategory ),
46+ log : log ,
47+ resolver : resolverProvider ,
48+ definitions : definitions ,
49+ typesCache : make (map [string ]* graphql.Object ),
50+ inputTypesCache : make (map [string ]* graphql.InputObject ),
51+ typeNameRegistry : make (map [string ]string ),
52+ enhancedRefTypesCache : make (map [string ]* graphql.Object ),
53+ typeByCategory : make (map [string ][]resolver.TypeByCategory ),
5154 }
5255
5356 err := g .generateGraphqlSchema ()
@@ -318,10 +321,31 @@ func (g *Gateway) generateGraphQLFields(resourceScheme *spec.Schema, typePrefix
318321 fields := graphql.Fields {}
319322 inputFields := graphql.InputObjectConfigFieldMap {}
320323
324+ // Extract GVK for relationship resolution
325+ // Only try to get GVK for main resource types, not nested types
326+ var currentGVK * schema.GroupVersionKind
327+ if len (fieldPath ) == 0 {
328+ // This is a main resource type, try to get its GVK
329+ currentGVK , _ = g .getGroupVersionKind (typePrefix )
330+ if currentGVK == nil {
331+ // Fallback: try to infer GVK from well-known type names
332+ currentGVK = g .getGVKForTypeName (typePrefix )
333+ }
334+ }
335+
336+ // Collect relationship fields for the relations field
337+ var relationshipFields []string
338+
321339 for fieldName , fieldSpec := range resourceScheme .Properties {
322340 sanitizedFieldName := sanitizeFieldName (fieldName )
323341 currentFieldPath := append (fieldPath , fieldName )
324342
343+ // Check if this is a relationship field and collect it
344+ if g .isRelationshipField (fieldName , fieldSpec ) {
345+ relationshipFields = append (relationshipFields , fieldName )
346+ }
347+
348+ // Regular field processing for ALL fields (including relationship fields)
325349 fieldType , inputFieldType , err := g .convertSwaggerTypeToGraphQL (fieldSpec , typePrefix , currentFieldPath , processingTypes )
326350 if err != nil {
327351 return nil , nil , err
@@ -336,6 +360,27 @@ func (g *Gateway) generateGraphQLFields(resourceScheme *spec.Schema, typePrefix
336360 }
337361 }
338362
363+ // Add individual relationship fields if we have them
364+ if len (relationshipFields ) == 0 || currentGVK == nil {
365+ return fields , inputFields , nil
366+ }
367+
368+ relationshipResolver := g .resolver .GetRelationshipResolver ()
369+ if relationshipResolver == nil {
370+ return fields , inputFields , nil
371+ }
372+
373+ for _ , fieldName := range relationshipFields {
374+ targetKind := g .extractTargetKind (fieldName )
375+ relationFieldName := strings .ToLower (string (targetKind [0 ])) + targetKind [1 :] + "Relation"
376+
377+ relationFieldType := g .createEnhancedRefType (targetKind )
378+ fields [relationFieldName ] = & graphql.Field {
379+ Type : relationFieldType ,
380+ Resolve : relationshipResolver .CreateSingleRelationResolver (* currentGVK , fieldName ),
381+ }
382+ }
383+
339384 return fields , inputFields , nil
340385}
341386
@@ -575,3 +620,75 @@ func sanitizeFieldName(name string) string {
575620
576621 return name
577622}
623+
624+ // isRelationshipField checks if a field is a relationship field
625+ func (g * Gateway ) isRelationshipField (fieldName string , fieldSpec spec.Schema ) bool {
626+ isRelationship := resolver .IsRelationshipField (fieldName )
627+ return isRelationship
628+ }
629+
630+ // extractTargetKind extracts the target kind from a relationship field name
631+ func (g * Gateway ) extractTargetKind (fieldName string ) string {
632+ return resolver .ExtractTargetKind (fieldName )
633+ }
634+
635+ // createEnhancedRefType creates a GraphQL type for enhanced references
636+ func (g * Gateway ) createEnhancedRefType (targetKind string ) graphql.Output {
637+ typeName := targetKind + "Relation"
638+
639+ // Check cache first
640+ if existingType , exists := g .enhancedRefTypesCache [typeName ]; exists {
641+ return existingType
642+ }
643+
644+ refType := graphql .NewObject (graphql.ObjectConfig {
645+ Name : typeName ,
646+ Fields : graphql.Fields {
647+ "kind" : & graphql.Field {
648+ Type : graphql .String ,
649+ Description : "Kind of the referenced resource" ,
650+ },
651+ "groupVersion" : & graphql.Field {
652+ Type : graphql .String ,
653+ Description : "GroupVersion of the referenced resource" ,
654+ },
655+ "name" : & graphql.Field {
656+ Type : graphql .NewNonNull (graphql .String ),
657+ Description : "Name of the referenced resource" ,
658+ },
659+ "namespace" : & graphql.Field {
660+ Type : graphql .String ,
661+ Description : "Namespace of the referenced resource" ,
662+ },
663+ },
664+ })
665+
666+ // Cache the type
667+ g .enhancedRefTypesCache [typeName ] = refType
668+ return refType
669+ }
670+
671+ // getGVKForTypeName gets the GroupVersionKind for a type name by looking up in existing definitions
672+ func (g * Gateway ) getGVKForTypeName (typeName string ) * schema.GroupVersionKind {
673+ // Try to find the type in our definitions by looking for a matching kind
674+ for resourceKey := range g .definitions {
675+ if gvk , err := g .getGroupVersionKind (resourceKey ); err == nil && gvk .Kind == typeName {
676+ return gvk
677+ }
678+ }
679+
680+ // Fallback: check if we have this in our type registry
681+ if groupVersion , exists := g .typeNameRegistry [typeName ]; exists {
682+ // Parse the group/version string
683+ parts := strings .Split (groupVersion , "/" )
684+ if len (parts ) == 2 {
685+ return & schema.GroupVersionKind {
686+ Group : parts [0 ],
687+ Version : parts [1 ],
688+ Kind : typeName ,
689+ }
690+ }
691+ }
692+
693+ return nil
694+ }
0 commit comments