ashutosh pal
ashutosh pal

Reputation: 21

How to define a custom GraphQL query complexity calculator?

Consider a schema like following:

type Users {
 name: String
 age: Int
 Posts: [Post!]
}

type Post {
 id: String
 title: String
 createdAt: Date
}

Now, I want to assign field-specific complexity to the above schema. For e.g. Posts field in the User can have more complexity as it can be a more expensive resolver. Also, the complexity can also depend on the number of posts we request in the query.

The official doc explains how to define custom complexity calculator and assign it to your schema but it doesn't work for my case due to following reasons:

What I’ve Tried

type User {
 name: String
 age: Int
 Posts: [Post!] @complexity(complexityDegree:"5")
}
type ComplexityMetadata struct {
    Value          int
}
// Extract complexity metadata for fields using introspection on ExecutableSchema
func extractComplexityMetadata(schema graphql.ExecutableSchema) map[string]ComplexityMetadata {
    metadata := make(map[string]ComplexityMetadata)

    // Access the underlying schema from ExecutableSchema
    underlyingSchema := schema.Schema()

    // Convert from gqlparser's AST schema to introspection schema (from gqlgen)
    collectComplexityMetadata(underlyingSchema, metadata, "")

    return metadata
}


func collectComplexityMetadata(schema *ast.Schema, metadata map[string]ComplexityMetadata, parentType string) {
    for _, typ := range schema.Types {
        // Skip non-object types and built-in GraphQL types
        log.Info("collecting complexity metadata", "type", typ.Name, "kind", typ.Kind)
        if typ.Kind != ast.Object {
            continue
        }

        // Iterate over fields in this type
        for _, field := range typ.Fields {
            fieldName := fmt.Sprintf("%s.%s", parentType, field.Name)

            // Check if this field has a `complexity` directive
            for _, directive := range field.Directives {
                if directive.Name == "complexity" {
                    // Extract the directive arguments (value and pageSizeFactor)
                    valueArg := directive.Arguments.ForName("complexityDegree")

                    complexityValue := 1 // Default complexity value

                    if valueArg != nil {
                        complexityValue, _ = strconv.Atoi(valueArg.Value.Raw) 
                    }

                    // Store the metadata using a string key (TypeName.FieldName)
                    metadata[fieldName] = ComplexityMetadata{
                        Value:         complexityValue,
                    }
                }
            }

            // If the field's type is an object type, recurse into it
            if field.Type.NamedType != "" && schema.Types[field.Type.NamedType].Kind == ast.Object {
                // Recurse into the nested type and collect its fields
                log.Info("recursing into nested type", "field", fieldName, "type", field.Type.NamedType)
                collectComplexityMetadata(schema, metadata, fieldName)
            }
        }
    }
}

This approach performs badly on large schema and takes too much time (8-10 minutes). gqlgen's official doc mentions assigning the function one by one to your schema. This is impractical in the case of a huge schema.

I want to dynamically assign custom complexity calculator to all types in the schema. Ideally in a way that doesn't take several minutes to process.

Upvotes: 2

Views: 74

Answers (0)

Related Questions