Skip to content

fix: map metadata to objectmeta #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 31, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions gateway/schema/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ func (g *Gateway) GenerateTypeNameForTest(typePrefix string, fieldPath []string)
func SanitizeFieldNameForTest(name string) string {
return sanitizeFieldName(name)
}

func (g *Gateway) ShouldInferAsObjectMetaForTest(fieldPath []string) bool {
return g.shouldInferAsObjectMeta(fieldPath)
}

func (g *Gateway) GetObjectMetaTypeForTest() (interface{}, interface{}, error) {
return g.getObjectMetaType()
}
92 changes: 92 additions & 0 deletions gateway/schema/metadata_inference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package schema_test

import (
"testing"

"github.com/openmfp/kubernetes-graphql-gateway/gateway/schema"
)

func TestShouldInferAsObjectMeta(t *testing.T) {
g := schema.GetGatewayForTest(map[string]string{})

tests := []struct {
name string
fieldPath []string
expected bool
}{
{
name: "marketplace_entry_api_export_metadata",
fieldPath: []string{"spec", "apiExport", "metadata"},
expected: true,
},
{
name: "marketplace_entry_provider_metadata_metadata",
fieldPath: []string{"spec", "providerMetadata", "metadata"},
expected: true,
},
{
name: "root_metadata_should_not_infer",
fieldPath: []string{"metadata"},
expected: false,
},
{
name: "non_metadata_field",
fieldPath: []string{"spec", "containers"},
expected: false,
},
{
name: "metadata_but_wrong_path",
fieldPath: []string{"spec", "someOtherField", "metadata"},
expected: false,
},
{
name: "empty_field_path",
fieldPath: []string{},
expected: false,
},
{
name: "metadata_at_wrong_level",
fieldPath: []string{"data", "metadata"},
expected: false,
},
{
name: "deeply_nested_but_not_allowlisted",
fieldPath: []string{"spec", "template", "spec", "metadata"},
expected: false,
},
{
name: "partial_match_should_not_infer",
fieldPath: []string{"spec", "apiExport"},
expected: false,
},
{
name: "case_sensitive_metadata",
fieldPath: []string{"spec", "apiExport", "Metadata"},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := g.ShouldInferAsObjectMetaForTest(tt.fieldPath)
if got != tt.expected {
t.Errorf("ShouldInferAsObjectMetaForTest(%v) = %v, want %v", tt.fieldPath, got, tt.expected)
}
})
}
}

func TestGetObjectMetaType_Fallback(t *testing.T) {
// Test that getObjectMetaType doesn't panic and returns something
g := schema.GetGatewayForTest(map[string]string{})

outputType, inputType, err := g.GetObjectMetaTypeForTest()

if err != nil {
t.Errorf("GetObjectMetaTypeForTest() unexpected error: %v", err)
}

if outputType == nil || inputType == nil {
t.Errorf("GetObjectMetaTypeForTest() should return non-nil types as fallback, got outputType=%v, inputType=%v", outputType, inputType)
}
}
62 changes: 60 additions & 2 deletions gateway/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,66 @@ func (g *Gateway) handleObjectFieldSpecType(fieldSpec spec.Schema, typePrefix st
}
}

// It's an empty object
return graphql.String, graphql.String, nil
// Check if this should be ObjectMeta
if g.shouldInferAsObjectMeta(fieldPath) {
return g.getObjectMetaType()
}

// It's an empty object, use StringMap as fallback
return stringMapScalar, stringMapScalar, nil
}

// shouldInferAsObjectMeta checks if a field path should be inferred as ObjectMeta
func (g *Gateway) shouldInferAsObjectMeta(fieldPath []string) bool {
if len(fieldPath) == 0 {
return false
}

// Check if the last field in the path is "metadata"
lastField := fieldPath[len(fieldPath)-1]
if lastField != "metadata" {
return false
}

// Define allowlist of known metadata fields that should be ObjectMeta
pathStr := strings.Join(fieldPath, ".")
knownMetadataPaths := []string{
"spec.apiExport.metadata",
"spec.providerMetadata.metadata",
}

for _, knownPath := range knownMetadataPaths {
if pathStr == knownPath {
return true
}
}

return false
}

// getObjectMetaType returns the ObjectMeta type from the schema definitions
func (g *Gateway) getObjectMetaType() (graphql.Output, graphql.Input, error) {
objectMetaKey := "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"

// First check if the ObjectMeta type is already cached (most common case)
if existingType, exists := g.typesCache[objectMetaKey]; exists {
existingInputType := g.inputTypesCache[objectMetaKey]
return existingType, existingInputType, nil
}

// If not cached, try to generate it using the same key as normal $ref processing
if g.definitions != nil {
if objectMetaSchema, exists := g.definitions[objectMetaKey]; exists {
return g.handleObjectFieldSpecType(objectMetaSchema, objectMetaKey, []string{}, make(map[string]bool))
}
}

// Log warning when ObjectMeta definition is missing but expected
if g.log != nil {
g.log.Error().Msg("ObjectMeta definition not found in schema, falling back to StringMap")
}

return stringMapScalar, stringMapScalar, nil
}

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