Documentation
¶
Overview ¶
Package graph provides a modern, secure GraphQL handler for Go with built-in authentication, validation, and an intuitive builder API.
Built on top of graphql-go (github.com/graphql-go/graphql), this package simplifies GraphQL server development with sensible defaults while maintaining full flexibility.
Features ¶
- Zero Config Start: Default hello world schema included
- Fluent Builder API: Clean, type-safe schema construction
- Built-in Authentication: Automatic Bearer token extraction
- Security First: Query depth, complexity, and introspection protection
- Response Sanitization: Remove field suggestions from errors
- Framework Agnostic: Works with net/http, Gin, Chi, or any HTTP framework
Quick Start ¶
Start immediately with the default schema:
import "github.com/paulmanoni/graph"
func main() {
handler := graph.NewHTTP(&graph.GraphContext{
Playground: true,
DEBUG: true,
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
}
Builder Pattern ¶
Use the fluent builder API for clean schema construction:
func getUser() graph.QueryField {
return graph.NewResolver[User]("user").
WithArgs(graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.String},
}).
WithResolver(func(p graphql.ResolveParams) (interface{}, error) {
id, _ := graph.GetArgString(p, "id")
return User{ID: id, Name: "Alice"}, nil
}).BuildQuery()
}
Authentication ¶
Automatic Bearer token extraction with optional user details fetching:
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getProtectedQuery()},
},
UserDetailsFn: func(ctx context.Context, token string) (context.Context, interface{}, error) {
user, err := validateAndGetUser(token)
if err != nil {
return ctx, nil, err
}
// Add values to context accessible via p.Context.Value() in resolvers
ctx = context.WithValue(ctx, "userID", user.ID)
return ctx, user, nil
},
})
Access token in resolvers:
func getProtectedQuery() graph.QueryField {
return graph.NewResolver[User]("me").
WithResolver(func(p graphql.ResolveParams) (interface{}, error) {
token, err := graph.GetRootString(p, "token")
if err != nil {
return nil, fmt.Errorf("authentication required")
}
// Use token...
}).BuildQuery()
}
Security ¶
Enable security features for production:
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false, // Enable security features
EnableValidation: true, // Max depth: 10, Max aliases: 4, Max complexity: 200
EnableSanitization: true, // Remove field suggestions from errors
Playground: false, // Disable playground in production
})
Helper Functions ¶
Extract arguments safely:
name, err := graph.GetArgString(p, "name") age, err := graph.GetArgInt(p, "age") active, err := graph.GetArgBool(p, "active")
Access root values:
token, err := graph.GetRootString(p, "token") var user User err := graph.GetRootInfo(p, "details", &user)
For more information, see https://github.com/paulmanoni/graph
Index ¶
- Constants
- Variables
- func AsyncFieldResolver(resolver graphql.FieldResolveFn) graphql.FieldResolveFn
- func CachedFieldResolver(cacheKey func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) graphql.FieldResolveFn
- func ConditionalResolver(condition func(graphql.ResolveParams) bool, ...) graphql.FieldResolveFn
- func DataTransformResolver(transform func(interface{}) interface{}) graphql.FieldResolveFn
- func ExecuteValidationRules(queryString string, schema *graphql.Schema, rules []ValidationRule, ...) error
- func ExtractBearerToken(r *http.Request) string
- func GenerateArgsFromStruct[T any]() graphql.FieldConfigArgument
- func GenerateGraphQLFields[T any]() graphql.Fields
- func GenerateGraphQLObject[T any](name string) *graphql.Object
- func GenerateInputObject[T any](name string) *graphql.InputObject
- func Get[T any](a ArgsGetter, name string) T
- func GetArg(p ResolveParams, key string, target interface{}) error
- func GetArgBool(p ResolveParams, key string) (bool, error)
- func GetArgInt(p ResolveParams, key string) (int, error)
- func GetArgString(p ResolveParams, key string) (string, error)
- func GetE[T any](a ArgsGetter, name string) (T, error)
- func GetOr[T any](a ArgsGetter, name string, defaultVal T) T
- func GetRoot[T any](r RootInfoGetter, name string) T
- func GetRootE[T any](r RootInfoGetter, name string) (T, error)
- func GetRootInfo(p ResolveParams, key string, target interface{}) error
- func GetRootOr[T any](r RootInfoGetter, name string, defaultVal T) T
- func GetRootString(p ResolveParams, key string) (string, error)
- func GetTypeName[T any]() string
- func LazyFieldResolver(fieldName string, loader func(interface{}) (interface{}, error)) graphql.FieldResolveFn
- func MergeRoleConfigs(configs ...map[string][]string) map[string][]string
- func MustGet[T any](a ArgsGetter, name string) T
- func MustGetRoot[T any](r RootInfoGetter, name string) T
- func New(graphCtx GraphContext) (*handler.Handler, error)
- func NewHTTP(graphCtx *GraphContext) http.HandlerFunc
- func NewWebSocketHandler(params WebSocketParams) http.HandlerFunc
- func PerUserBudgetFunc(budgets map[string]int, defaultBudget int) func(string) (int, error)
- func RegisterObjectType(name string, typeFactory func() *graphql.Object) *graphql.Object
- func SimpleBudgetFunc(budget int) func(string) (int, error)
- func UnmarshalSubscriptionMessage[T any](msg *Message) (*T, error)
- func ValidateGraphQLQuery(queryString string, schema *graphql.Schema) error
- type ASTVisitor
- type Args
- type ArgsGetter
- type ArgsMap
- type BaseRule
- func (r *BaseRule) Disable()
- func (r *BaseRule) Enable()
- func (r *BaseRule) Enabled() bool
- func (r *BaseRule) Name() string
- func (r *BaseRule) NewError(message string) *ValidationError
- func (r *BaseRule) NewErrorf(format string, args ...interface{}) *ValidationError
- func (r *BaseRule) SetEnabled(enabled bool)
- type BlockedFieldsRule
- type Connection
- type FieldConfig
- type FieldGenerator
- type FieldMiddleware
- type FieldResolveFn
- type GenericTypeInfo
- type GraphContext
- type HasIDInterface
- type HasPermissionsInterface
- type HasRolesInterface
- type InMemoryPubSub
- func (p *InMemoryPubSub) Close() error
- func (p *InMemoryPubSub) Publish(ctx context.Context, topic string, data interface{}) error
- func (p *InMemoryPubSub) Subscribe(ctx context.Context, topic string) <-chan *Message
- func (p *InMemoryPubSub) Unsubscribe(ctx context.Context, subscriptionID string) error
- type JSONTime
- type MaxAliasesRule
- type MaxComplexityRule
- type MaxDepthRule
- type MaxTokensRule
- type Message
- type MultiValidationError
- type MutationField
- type NoIntrospectionRule
- type PageInfo
- type PaginatedResponse
- type PaginationArgs
- type PermissionRule
- type PermissionRules
- type PubSub
- type QueryField
- type RateLimitOption
- type RateLimitRule
- type RequireAuthRule
- type ResolveParams
- type RoleRule
- type RoleRules
- type RootInfo
- type RootInfoGetter
- type SchemaBuilder
- type SchemaBuilderParams
- type SubscriptionField
- type SubscriptionFilterFn
- type SubscriptionResolveFn
- type SubscriptionResolver
- func (s *SubscriptionResolver[T]) BuildSubscription() SubscriptionField
- func (s *SubscriptionResolver[T]) WithArgs(args graphql.FieldConfigArgument) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithDescription(desc string) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFilter(fn SubscriptionFilterFn[T]) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithMiddleware(middleware FieldMiddleware) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithResolver(fn SubscriptionResolveFn[T]) *SubscriptionResolver[T]
- type TypedArgsResolverdeprecated
- func (r *TypedArgsResolver[T, A]) AsList() *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) AsPaginated() *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) BuildMutation() MutationField
- func (r *TypedArgsResolver[T, A]) BuildQuery() QueryField
- func (r *TypedArgsResolver[T, A]) WithDescription(desc string) *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) WithResolver(resolver func(ctx context.Context, p ResolveParams, args A) (*T, error)) *TypedArgsResolver[T, A]
- type UnifiedResolver
- func (r *UnifiedResolver[T]) AsList() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) AsMutation() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) AsPaginated() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) Build() interface{}
- func (r *UnifiedResolver[T]) BuildMutation() MutationField
- func (r *UnifiedResolver[T]) BuildQuery() QueryField
- func (r *UnifiedResolver[T]) Name() string
- func (r *UnifiedResolver[T]) Serve() *graphql.Field
- func (r *UnifiedResolver[T]) WithArg(name string, argType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithArgDefault(name string, argType interface{}, defaultValue interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithArgRequired(name string, argType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithArgs(args graphql.FieldConfigArgument) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithArgsFromStruct(structType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithAsyncField(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithCachedField(fieldName string, cacheKeyFunc func(graphql.ResolveParams) string, ...) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithComputedField(name string, fieldType graphql.Output, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithCustomField(name string, field *graphql.Field) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithDescription(desc string) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldResolvers(overrides map[string]graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObject(inputType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObjectFieldName(name string) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObjectNullable() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithLazyField(fieldName string, loader func(interface{}) (interface{}, error)) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithMiddleware(middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithPermission(middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithResolver(resolver func(ResolveParams) (*T, error)) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithTypedResolver(typedResolver interface{}) *UnifiedResolver[T]
- type ValidationContext
- type ValidationError
- type ValidationOptions
- type ValidationRule
- func CombineRules(ruleSets ...[]ValidationRule) []ValidationRule
- func DefaultValidationRules() []ValidationRule
- func DevelopmentValidationRules() []ValidationRule
- func NewBlockedFieldsRule(fields ...string) ValidationRule
- func NewMaxAliasesRule(maxAliases int) ValidationRule
- func NewMaxComplexityRule(maxComplexity int) ValidationRule
- func NewMaxDepthRule(maxDepth int) ValidationRule
- func NewMaxTokensRule(maxTokens int) ValidationRule
- func NewNoIntrospectionRule() ValidationRule
- func NewPermissionRule(field string, permissions ...string) ValidationRule
- func NewPermissionRules(config map[string][]string) ValidationRule
- func NewRateLimitRule(opts ...RateLimitOption) ValidationRule
- func NewRequireAuthRule(targets ...string) ValidationRule
- func NewRoleRule(field string, roles ...string) ValidationRule
- func NewRoleRules(config map[string][]string) ValidationRule
- func ProductionValidationRules() []ValidationRule
- type WSMessage
- type WebSocketManager
- type WebSocketParams
Constants ¶
const ( // Client -> Server (graphql-ws) MessageTypeConnectionInit = "connection_init" MessageTypeSubscribe = "subscribe" MessageTypePing = "ping" // Client -> Server (subscriptions-transport-ws - legacy) MessageTypeStart = "start" // Legacy equivalent of "subscribe" MessageTypeStop = "stop" // Legacy equivalent of "complete" // Server -> Client (graphql-ws) MessageTypeConnectionAck = "connection_ack" MessageTypeNext = "next" MessageTypeError = "error" MessageTypeComplete = "complete" MessageTypePong = "pong" // Server -> Client (subscriptions-transport-ws - legacy) MessageTypeData = "data" // Legacy equivalent of "next" MessageTypeConnectionError = "connection_error" // Legacy connection error MessageTypeConnectionKeepAlive = "ka" // Legacy keep-alive )
GraphQL WebSocket Protocol message types Supports both graphql-ws (new) and subscriptions-transport-ws (legacy) protocols
const SpringShortLayout = "2006-01-02T15:04"
SpringShortLayout is the time format used by Spring Boot for DateTime serialization. Format: yyyy-MM-dd'T'HH:mm (e.g., "2024-01-15T14:30")
Variables ¶
var ( String = graphql.String Int = graphql.Int Float = graphql.Float Boolean = graphql.Boolean ID = graphql.ID )
GraphQL scalar type constants for use with WithArg These mirror Go's type names for intuitive usage:
WithArg("id", graph.String) // like using 'string'
WithArg("limit", graph.Int) // like using 'int'
var ( // SecurityRules provides standard security validation // - Max depth: 10 // - Max complexity: 200 // - Max aliases: 4 // - No introspection SecurityRules = []ValidationRule{ NewMaxDepthRule(10), NewMaxComplexityRule(200), NewMaxAliasesRule(4), NewNoIntrospectionRule(), } // StrictSecurityRules provides strict security for production // - Max depth: 8 // - Max complexity: 150 // - Max aliases: 3 // - Max tokens: 500 // - No introspection StrictSecurityRules = []ValidationRule{ NewMaxDepthRule(8), NewMaxComplexityRule(150), NewMaxAliasesRule(3), NewMaxTokensRule(500), NewNoIntrospectionRule(), } // DevelopmentRules provides lenient rules for development // - Max depth: 20 // - Max complexity: 500 DevelopmentRules = []ValidationRule{ NewMaxDepthRule(20), NewMaxComplexityRule(500), } )
var ( // AdminOnlyFields - fields that require admin role AdminOnlyFields = map[string][]string{ "deleteUser": {"admin"}, "deleteAccount": {"admin"}, "viewAuditLog": {"admin"}, "systemSettings": {"admin"}, "manageRoles": {"admin"}, } // ManagerFields - fields that require admin or manager role ManagerFields = map[string][]string{ "approveOrder": {"admin", "manager"}, "viewReports": {"admin", "manager"}, "manageTeam": {"admin", "manager"}, "bulkOperations": {"admin", "manager"}, } // AuditorFields - fields that require admin or auditor role AuditorFields = map[string][]string{ "viewAuditLog": {"admin", "auditor"}, "exportLogs": {"admin", "auditor"}, "viewAnalytics": {"admin", "auditor"}, } )
Common role configurations for convenience
var ( ErrPubSubClosed = newError("pubsub is closed") ErrSubscriptionNotFound = newError("subscription not found") )
Common errors
var DateTime = graphql.NewScalar(graphql.ScalarConfig{ Name: "DateTime", Description: "The `DateTime` scalar type formatted as yyyy-MM-dd'T'HH:mm", Serialize: serializeDateTime, ParseValue: unserializeDateTime, ParseLiteral: func(valueAST ast.Value) interface{} { if v, ok := valueAST.(*ast.StringValue); ok { return unserializeDateTime(v.Value) } return nil }, })
DateTime is a GraphQL scalar type for date-time values. It uses the Spring Boot date format: yyyy-MM-dd'T'HH:mm (e.g., "2024-01-15T14:30"). All times are automatically converted to UTC.
Usage in struct fields:
type Event struct {
Name string `json:"name"`
StartTime time.Time `json:"startTime"` // Will use DateTime scalar
}
The scalar automatically handles:
- Serialization: time.Time → "2024-01-15T14:30"
- Deserialization: "2024-01-15T14:30" → time.Time
- UTC conversion for all values
Functions ¶
func AsyncFieldResolver ¶
func AsyncFieldResolver(resolver graphql.FieldResolveFn) graphql.FieldResolveFn
AsyncFieldResolver executes a resolver asynchronously
func CachedFieldResolver ¶
func CachedFieldResolver(cacheKey func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) graphql.FieldResolveFn
CachedFieldResolver caches field results with a key function
func ConditionalResolver ¶
func ConditionalResolver(condition func(graphql.ResolveParams) bool, ifTrue, ifFalse graphql.FieldResolveFn) graphql.FieldResolveFn
ConditionalResolver resolves based on a condition
func DataTransformResolver ¶
func DataTransformResolver(transform func(interface{}) interface{}) graphql.FieldResolveFn
DataTransformResolver applies a transformation to a field value
func ExecuteValidationRules ¶ added in v1.1.1
func ExecuteValidationRules( queryString string, schema *graphql.Schema, rules []ValidationRule, userDetails interface{}, options *ValidationOptions, ) error
ExecuteValidationRules executes a set of validation rules against a GraphQL query. This is the modern validation system that supports custom rules.
Parameters:
- queryString: The GraphQL query to validate
- schema: The GraphQL schema
- rules: The validation rules to execute
- authCtx: Authentication context (can be nil if not needed)
- options: Validation options (can be nil for defaults)
Returns:
- nil if validation passes
- *ValidationError for single rule failure
- *MultiValidationError for multiple rule failures
Example:
rules := []ValidationRule{
NewMaxDepthRule(10),
NewRequireAuthRule("mutation"),
NewRoleRules(AdminOnlyFields),
}
err := ExecuteValidationRules(query, schema, rules, authCtx, nil)
func ExtractBearerToken ¶
ExtractBearerToken extracts the Bearer token from the Authorization header. It performs case-insensitive matching for the "Bearer " prefix and trims whitespace.
Returns an empty string if:
- The Authorization header is missing
- The header doesn't start with "Bearer " (case-insensitive)
- The token value is empty
Example:
// Authorization: Bearer abc123xyz token := graph.ExtractBearerToken(r) // Returns: "abc123xyz"
func GenerateArgsFromStruct ¶
func GenerateArgsFromStruct[T any]() graphql.FieldConfigArgument
func GenerateGraphQLFields ¶
func GenerateInputObject ¶
func GenerateInputObject[T any](name string) *graphql.InputObject
func Get ¶ added in v1.1.7
func Get[T any](a ArgsGetter, name string) T
Get retrieves an argument by name with type safety. Returns zero value if key is missing or conversion fails. Use GetE for explicit error handling.
Works with both graph.Args (from WithArg) and graph.ArgsMap (from p.Args):
// With graph.Args (from WithArg chainable API) id := graph.Get[string](args, "id") // With p.Args (from graphql.FieldConfigArgument) channelID := graph.Get[string](graph.ArgsMap(p.Args), "channelID")
func GetArg ¶
func GetArg(p ResolveParams, key string, target interface{}) error
GetArg safely extracts a value from p.Args and unmarshals it into the target. This is useful for extracting complex types like structs, slices, or maps.
The function handles:
- Primitive types (string, int, bool) with optimized direct assignment
- Complex types using JSON marshaling/unmarshaling for type conversion
- Type mismatches with descriptive error messages
Returns an error if:
- The argument key doesn't exist
- Type conversion fails
Example:
var input CreateUserInput
if err := graph.GetArg(p, "input", &input); err != nil {
return nil, err
}
// Use input.Name, input.Email, etc.
func GetArgBool ¶
func GetArgBool(p ResolveParams, key string) (bool, error)
GetArgBool safely extracts a bool argument from p.Args. Returns an error if the argument doesn't exist or is not a boolean.
Example:
active, err := graph.GetArgBool(p, "active")
func GetArgInt ¶
func GetArgInt(p ResolveParams, key string) (int, error)
GetArgInt safely extracts an int argument from p.Args. Handles both int and float64 types (JSON numbers are parsed as float64). Returns an error if the argument doesn't exist or is not a number.
Example:
age, err := graph.GetArgInt(p, "age")
func GetArgString ¶
func GetArgString(p ResolveParams, key string) (string, error)
GetArgString safely extracts a string argument from p.Args. Returns an error if the argument doesn't exist or is not a string.
Example:
name, err := graph.GetArgString(p, "name")
func GetE ¶ added in v1.1.8
func GetE[T any](a ArgsGetter, name string) (T, error)
GetE retrieves an argument by name with type safety and error handling. Returns an error if the key is missing or type conversion fails.
Works with both graph.Args and graph.ArgsMap (p.Args):
// With graph.Args input, err := graph.GetE[UserInput](args, "input") // With p.Args channelID, err := graph.GetE[string](graph.ArgsMap(p.Args), "channelID")
func GetOr ¶ added in v1.1.7
func GetOr[T any](a ArgsGetter, name string, defaultVal T) T
GetOr retrieves an argument by name with a default value if not found.
Works with both graph.Args and graph.ArgsMap (p.Args):
// With graph.Args limit := graph.GetOr[int](args, "limit", 10) // With p.Args limit := graph.GetOr[int](graph.ArgsMap(p.Args), "limit", 10)
func GetRoot ¶ added in v1.2.2
func GetRoot[T any](r RootInfoGetter, name string) T
GetRoot retrieves a value from root info by name with type safety. Returns zero value if key is missing or conversion fails. Use GetRootE for explicit error handling.
Usage:
user := graph.GetRoot[UserDetails](graph.NewRootInfo(p), "details") token := graph.GetRoot[string](graph.NewRootInfo(p), "token")
func GetRootE ¶ added in v1.2.2
func GetRootE[T any](r RootInfoGetter, name string) (T, error)
GetRootE retrieves a value from root info by name with type safety and error handling. Returns an error if the key is missing or type conversion fails.
Usage:
user, err := graph.GetRootE[UserDetails](graph.NewRootInfo(p), "details")
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
func GetRootInfo ¶
func GetRootInfo(p ResolveParams, key string, target interface{}) error
GetRootInfo safely extracts a value from p.Info.RootValue and unmarshals it into the target. This is commonly used to retrieve user details set by UserDetailsFn in the GraphContext.
The function handles:
- Primitive types (string, int) with optimized direct assignment
- Complex types using JSON marshaling/unmarshaling for type conversion
- Type mismatches with descriptive error messages
Returns an error if:
- Root value is nil or not a map
- The key doesn't exist in the root value
- Type conversion fails
Example:
// In your resolver
var user UserDetails
if err := graph.GetRootInfo(p, "details", &user); err != nil {
return nil, fmt.Errorf("authentication required")
}
// Use user.ID, user.Email, etc.
func GetRootOr ¶ added in v1.2.2
func GetRootOr[T any](r RootInfoGetter, name string, defaultVal T) T
GetRootOr retrieves a value from root info by name with a default value if not found.
Usage:
token := graph.GetRootOr[string](graph.NewRootInfo(p), "token", "anonymous") userID := graph.GetRootOr[int](graph.NewRootInfo(p), "userID", 0)
func GetRootString ¶
func GetRootString(p ResolveParams, key string) (string, error)
GetRootString safely extracts a string value from p.Info.RootValue. This is commonly used to retrieve the authentication token.
Returns an error if:
- Root value is nil or not a map
- The key doesn't exist in the root value
- The value is not a string
Example:
// Get authentication token
token, err := graph.GetRootString(p, "token")
if err != nil {
return nil, fmt.Errorf("authentication required")
}
// Validate token...
func GetTypeName ¶
func LazyFieldResolver ¶
func LazyFieldResolver(fieldName string, loader func(interface{}) (interface{}, error)) graphql.FieldResolveFn
LazyFieldResolver loads a field only when requested
func MergeRoleConfigs ¶ added in v1.1.1
MergeRoleConfigs combines multiple role configurations
Example:
allRoles := MergeRoleConfigs(AdminOnlyFields, ManagerFields, AuditorFields)
func MustGet ¶ added in v1.1.8
func MustGet[T any](a ArgsGetter, name string) T
MustGet retrieves an argument by name with type safety. Panics if the key is missing or conversion fails. Use only when you're certain the argument exists and is valid.
Works with both graph.Args and graph.ArgsMap (p.Args).
func MustGetRoot ¶ added in v1.2.2
func MustGetRoot[T any](r RootInfoGetter, name string) T
MustGetRoot retrieves a value from root info by name with type safety. Panics if the key is missing or conversion fails. Use only when you're certain the value exists and is valid.
Usage:
user := graph.MustGetRoot[UserDetails](graph.NewRootInfo(p), "details")
func New ¶
func New(graphCtx GraphContext) (*handler.Handler, error)
New creates a GraphQL handler from the provided GraphContext. It builds the schema and sets up authentication with token extraction and user details.
The handler automatically:
- Extracts tokens using TokenExtractorFn (defaults to Bearer token extraction)
- Fetches user details using UserDetailsFn if provided
- Adds token and details to the root value for access in resolvers
Returns an error if schema building fails.
Example:
handler, err := graph.New(graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
Playground: true,
})
func NewHTTP ¶
func NewHTTP(graphCtx *GraphContext) http.HandlerFunc
NewHTTP creates a standard http.HandlerFunc with built-in validation and sanitization support. This is the recommended way to create a GraphQL handler for production use.
The handler automatically detects WebSocket upgrade requests and handles them appropriately when subscriptions are enabled (EnableSubscriptions: true).
The handler is fully compatible with net/http and any HTTP framework (Gin, Chi, Echo, etc.). If graphCtx is nil, defaults to DEBUG mode with Playground enabled.
Behavior:
- In DEBUG mode (DEBUG: true): Skips all validation and sanitization for easier development
- In production (DEBUG: false): Enables validation and sanitization based on configuration
- Panics during initialization if schema building fails (fail-fast approach)
- WebSocket upgrade requests are handled when EnableSubscriptions: true
Security Features (when DEBUG: false):
- EnableValidation: Validates query depth (max 10), aliases (max 4), complexity (max 200), and blocks introspection
- EnableSanitization: Removes field suggestions from error messages to prevent information disclosure
Example without subscriptions:
// Development setup
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
DEBUG: true,
Playground: true,
})
// Production setup
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false,
EnableValidation: true,
EnableSanitization: true,
Playground: false,
UserDetailsFn: func(ctx context.Context, token string) (context.Context, interface{}, error) {
user, err := validateToken(token)
if err != nil {
return ctx, nil, err
}
// Add user ID to context for access in resolvers via p.Context.Value("userID")
ctx = context.WithValue(ctx, "userID", user.ID)
return ctx, user, nil
},
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
Example with subscriptions:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
MutationFields: []graph.MutationField{createUserMutation()},
SubscriptionFields: []graph.SubscriptionField{userSubscription(pubsub)},
},
PubSub: pubsub,
EnableSubscriptions: true,
DEBUG: false,
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
func NewWebSocketHandler ¶ added in v1.1.0
func NewWebSocketHandler(params WebSocketParams) http.HandlerFunc
NewWebSocketHandler creates an HTTP handler for WebSocket connections. This handler upgrades HTTP connections to WebSocket and manages GraphQL subscriptions.
Example:
params := graph.WebSocketParams{
Schema: schema,
PubSub: pubsub,
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://example.com"
},
AuthFn: func(r *http.Request) (interface{}, error) {
token := ExtractBearerToken(r)
return validateToken(token)
},
}
http.Handle("/graphql", graph.NewWebSocketHandler(params))
func PerUserBudgetFunc ¶ added in v1.1.1
PerUserBudgetFunc creates a budget function with per-user budgets Useful for different tier users or testing
func RegisterObjectType ¶
RegisterObjectType registers a GraphQL object type in the global registry Returns existing type if already registered, otherwise creates and registers new type
func SimpleBudgetFunc ¶ added in v1.1.1
SimpleBudgetFunc creates a simple budget function that returns a fixed budget Useful for testing or simple rate limiting scenarios
func UnmarshalSubscriptionMessage ¶ added in v1.1.0
Helper function to unmarshal subscription messages
func ValidateGraphQLQuery ¶
ValidateGraphQLQuery validates a GraphQL query against security rules. This function implements multiple layers of protection against malicious or expensive queries.
Validation Rules:
- Max Query Depth: 10 levels (prevents deeply nested queries)
- Max Aliases: 4 per query (prevents alias-based DoS attacks)
- Max Complexity: 200 (prevents computationally expensive queries)
- Introspection: Blocked (__schema and __type queries are rejected)
Returns an error if:
- Query depth exceeds 10 levels
- Query contains more than 4 aliases
- Query complexity exceeds 200
- Query contains __schema or __type introspection fields
- Query parsing fails (though parsing errors are allowed to pass through)
Example usage:
if err := graph.ValidateGraphQLQuery(queryString, schema); err != nil {
// Reject query with HTTP 400
return fmt.Errorf("invalid query: %w", err)
}
// Query is safe to execute
Enable this in production with GraphContext.EnableValidation = true.
Types ¶
type ASTVisitor ¶ added in v1.1.1
type ASTVisitor struct {
EnterField func(field *ast.Field, ctx *ValidationContext) error
LeaveField func(field *ast.Field, ctx *ValidationContext) error
EnterOperation func(op *ast.OperationDefinition, ctx *ValidationContext) error
LeaveOperation func(op *ast.OperationDefinition, ctx *ValidationContext) error
EnterFragment func(frag *ast.FragmentDefinition, ctx *ValidationContext) error
LeaveFragment func(frag *ast.FragmentDefinition, ctx *ValidationContext) error
}
ASTVisitor allows traversing the AST with hooks
type Args ¶ added in v1.1.7
type Args struct {
// contains filtered or unexported fields
}
Args provides type-safe access to GraphQL arguments
type ArgsGetter ¶ added in v1.1.9
ArgsGetter is an interface for types that can provide arguments by name. This allows Get[T], GetE[T], GetOr[T], and MustGet[T] to work with both:
- graph.Args (from WithArg chainable API)
- graph.ArgsMap (from graphql.FieldConfigArgument / p.Args)
type ArgsMap ¶ added in v1.1.9
type ArgsMap map[string]interface{}
ArgsMap wraps map[string]interface{} to implement ArgsGetter. This allows using p.Args directly with Get[T], GetE[T], etc.
Usage:
// In subscription resolvers with graphql.FieldConfigArgument channelID := graph.Get[string](graph.ArgsMap(p.Args), "channelID")
type BaseRule ¶ added in v1.1.1
type BaseRule struct {
// contains filtered or unexported fields
}
BaseRule provides common functionality for all validation rules All custom rules should embed this struct
func NewBaseRule ¶ added in v1.1.1
NewBaseRule creates a new base rule with the given name
func (*BaseRule) NewError ¶ added in v1.1.1
func (r *BaseRule) NewError(message string) *ValidationError
NewValidationError creates a validation error for this rule
func (*BaseRule) NewErrorf ¶ added in v1.1.1
func (r *BaseRule) NewErrorf(format string, args ...interface{}) *ValidationError
NewErrorf creates a validation error with formatted message
func (*BaseRule) SetEnabled ¶ added in v1.1.1
SetEnabled sets the enabled state
type BlockedFieldsRule ¶ added in v1.1.1
type BlockedFieldsRule struct {
BaseRule
// contains filtered or unexported fields
}
BlockedFieldsRule blocks specific fields from being queried
func (*BlockedFieldsRule) BlockField ¶ added in v1.1.1
func (r *BlockedFieldsRule) BlockField(field string, reason string) *BlockedFieldsRule
BlockField adds a field to the blocked list with an optional reason
func (*BlockedFieldsRule) Validate ¶ added in v1.1.1
func (r *BlockedFieldsRule) Validate(ctx *ValidationContext) error
type Connection ¶ added in v1.1.0
type Connection struct {
// contains filtered or unexported fields
}
Connection represents a single WebSocket connection.
type FieldConfig ¶
type FieldConfig struct {
Resolver graphql.FieldResolveFn
Description string
Args graphql.FieldConfigArgument
DeprecationReason string
}
type FieldGenerator ¶
type FieldGenerator[T any] struct { // contains filtered or unexported fields }
func NewFieldGenerator ¶
func NewFieldGenerator[T any]() *FieldGenerator[T]
type FieldMiddleware ¶
type FieldMiddleware func(next FieldResolveFn) FieldResolveFn
FieldMiddleware wraps a field resolver with additional functionality (auth, logging, caching, etc.)
func AuthMiddleware ¶
func AuthMiddleware(requiredRole string) FieldMiddleware
AuthMiddleware requires a specific user role
func CacheMiddleware ¶
func CacheMiddleware(cacheKey func(ResolveParams) string) FieldMiddleware
CacheMiddleware caches field results based on a key function
type FieldResolveFn ¶
type FieldResolveFn func(p ResolveParams) (interface{}, error)
func LoggingMiddleware ¶
func LoggingMiddleware(next FieldResolveFn) FieldResolveFn
LoggingMiddleware logs field resolution time
type GenericTypeInfo ¶
type GenericTypeInfo struct {
IsGeneric bool
IsWrapper bool
BaseTypeName string
ElementType reflect.Type
WrapperFields map[string]reflect.Type
}
GenericTypeInfo holds information about a generic type
type GraphContext ¶
type GraphContext struct {
// Schema: Provide either Schema OR SchemaParams (not both)
// If both are nil, a default "hello world" schema will be created
Schema *graphql.Schema
// SchemaParams: Alternative to Schema - will be built automatically
// If nil and Schema is also nil, defaults to hello world query/mutation
SchemaParams *SchemaBuilderParams
// PubSub: PubSub system for subscriptions (optional, only needed for subscriptions)
// Use NewInMemoryPubSub() for development or RedisPubSub for production
PubSub PubSub
// EnableSubscriptions: Enable WebSocket support for GraphQL subscriptions
// Default: false (subscriptions disabled)
// Requires PubSub to be configured
EnableSubscriptions bool
// WebSocketPath: Path for WebSocket endpoint (default: same as HTTP endpoint)
// If not set, WebSocket connections will be handled on the same path as HTTP
WebSocketPath string
// WebSocketCheckOrigin: Custom function to check WebSocket upgrade origin
// If not provided, all origins are allowed (only use in development!)
WebSocketCheckOrigin func(r *http.Request) bool
// Pretty: Pretty-print JSON responses
Pretty bool
// GraphiQL: Enable GraphiQL interface (deprecated, use Playground instead)
GraphiQL bool
// Playground: Enable GraphQL Playground interface
Playground bool
// DEBUG mode skips validation and sanitization for easier development
// Default: false (validation enabled)
DEBUG bool
// RootObjectFn: Custom function to set up root object for each request
// Called before token extraction and user details fetching
RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
// TokenExtractorFn: Custom token extraction from request
// If not provided, default Bearer token extraction will be used
TokenExtractorFn func(*http.Request) string
// UserDetailsFn: Custom user details fetching based on token
// If not provided, user details will not be added to rootValue
// The details are accessible in resolvers via GetRootInfo(p, "details", &user)
//
// The function receives the request context and token, and returns:
// - ctx: Updated context with custom values (accessible via p.Context.Value() in resolvers)
// - details: User details (accessible via GetRootInfo(p, "details", &user) in resolvers)
// - error: Any error during user details fetching
//
// Example:
//
// UserDetailsFn: func(ctx context.Context, token string) (context.Context, interface{}, error) {
// user, err := validateJWT(token)
// if err != nil {
// return ctx, nil, err
// }
// // Add user ID to context for access in resolvers via p.Context.Value("userID")
// ctx = context.WithValue(ctx, "userID", user.ID)
// return ctx, user, nil
// }
UserDetailsFn func(ctx context.Context, token string) (context.Context, interface{}, error)
// EnableValidation: Enable query validation (depth, complexity, introspection checks)
// Default: false (validation disabled)
// When enabled: Max depth=10, Max aliases=4, Max complexity=200, Introspection blocked
// DEPRECATED: Use ValidationRules for more control
EnableValidation bool
// ValidationRules: Custom validation rules (takes precedence over EnableValidation)
// Set to nil or empty slice to disable validation
// Example:
// ValidationRules: []ValidationRule{
// NewMaxDepthRule(10),
// NewRequireAuthRule("mutation"),
// NewRoleRules(map[string][]string{
// "deleteUser": {"admin"},
// }),
// }
ValidationRules []ValidationRule
// ValidationOptions: Configure validation behavior (optional)
// Default: StopOnFirstError=false, SkipInDebug=true
ValidationOptions *ValidationOptions
// EnableSanitization: Enable response sanitization (removes field suggestions from errors)
// Default: false (sanitization disabled)
// Prevents information disclosure by removing "Did you mean X?" suggestions
EnableSanitization bool
}
GraphContext configures a GraphQL handler with schema, authentication, and security settings.
Schema Configuration (choose one):
- Schema: Use a pre-built graphql.Schema
- SchemaParams: Use the builder pattern with QueryFields and MutationFields
- Neither: A default "hello world" schema will be created
Security Modes:
- DEBUG mode (DEBUG: true): Disables all validation and sanitization for development
- Production mode (DEBUG: false): Enables validation and sanitization based on configuration flags
Authentication:
- TokenExtractorFn: Extract tokens from requests (defaults to Bearer token extraction)
- UserDetailsFn: Fetch user details from the extracted token
- RootObjectFn: Custom root object setup for advanced use cases
Example Development Setup:
ctx := &graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
DEBUG: true,
Playground: true,
}
Example Production Setup:
ctx := &graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false,
EnableValidation: true, // Max depth: 10, Max aliases: 4, Max complexity: 200
EnableSanitization: true, // Remove field suggestions from errors
Playground: false,
UserDetailsFn: func(ctx context.Context, token string) (context.Context, interface{}, error) {
user, err := validateJWT(token)
if err != nil {
return ctx, nil, err
}
ctx = context.WithValue(ctx, "userID", user.ID)
return ctx, user, nil
},
}
type HasIDInterface ¶ added in v1.1.1
type HasIDInterface interface {
GetID() string
}
HasIDInterface - implement this on your user struct for rate limiting
type HasPermissionsInterface ¶ added in v1.1.1
HasPermissionsInterface - implement this on your user struct for permission-based rules
type HasRolesInterface ¶ added in v1.1.1
HasRolesInterface - implement this on your user struct for role-based rules
type InMemoryPubSub ¶ added in v1.1.0
type InMemoryPubSub struct {
// contains filtered or unexported fields
}
InMemoryPubSub is a simple in-memory implementation of PubSub. It's suitable for development, testing, and single-instance deployments. For production multi-instance deployments, use RedisPubSub or similar.
func NewInMemoryPubSub ¶ added in v1.1.0
func NewInMemoryPubSub() *InMemoryPubSub
NewInMemoryPubSub creates a new in-memory PubSub implementation.
Example:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
ctx := context.Background()
sub := pubsub.Subscribe(ctx, "events")
go func() {
for msg := range sub {
fmt.Println("Event:", string(msg.Data))
}
}()
pubsub.Publish(ctx, "events", map[string]string{"type": "user_created"})
func (*InMemoryPubSub) Close ¶ added in v1.1.0
func (p *InMemoryPubSub) Close() error
Close shuts down the PubSub and closes all active subscriptions.
func (*InMemoryPubSub) Publish ¶ added in v1.1.0
func (p *InMemoryPubSub) Publish(ctx context.Context, topic string, data interface{}) error
Publish sends data to all subscribers of the topic. Slow subscribers are skipped to prevent blocking.
func (*InMemoryPubSub) Subscribe ¶ added in v1.1.0
func (p *InMemoryPubSub) Subscribe(ctx context.Context, topic string) <-chan *Message
Subscribe creates a subscription to a topic. The subscription is automatically cleaned up when the context is canceled.
func (*InMemoryPubSub) Unsubscribe ¶ added in v1.1.0
func (p *InMemoryPubSub) Unsubscribe(ctx context.Context, subscriptionID string) error
Unsubscribe removes a subscription by ID (not commonly used with context-based cleanup).
type JSONTime ¶
JSONTime is a custom time type that handles flexible JSON time formats. It supports both RFC3339 strings and array formats commonly used in some APIs.
Supported input formats:
- RFC3339 string: "2024-01-15T14:30:00Z"
- Array format: [year, month, day, hour, minute, second, nanosecond]
- Minimum array: [2024, 1, 15] (time components default to 0)
Output format:
- Always RFC3339 string: "2024-01-15T14:30:00Z"
Example usage:
type Event struct {
Name string `json:"name"`
StartTime graph.JSONTime `json:"startTime"`
}
// Accepts: {"startTime": "2024-01-15T14:30:00Z"}
// Accepts: {"startTime": [2024, 1, 15, 14, 30, 0]}
// Outputs: {"startTime": "2024-01-15T14:30:00Z"}
func (JSONTime) MarshalJSON ¶
MarshalJSON implements json.Marshaler interface. Serializes JSONTime to RFC3339 format string.
func (JSONTime) Time ¶
Time converts JSONTime back to the standard time.Time type. This is useful when you need to perform time operations or comparisons.
Example:
var event Event
json.Unmarshal(data, &event)
standardTime := event.StartTime.Time()
// Now use standard time operations
if standardTime.After(time.Now()) { ... }
func (*JSONTime) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler interface. Deserializes from either RFC3339 string or array format.
Array format: [year, month, day, hour?, minute?, second?, nanosecond?] Missing time components default to 0. All times are assumed to be UTC.
type MaxAliasesRule ¶ added in v1.1.1
type MaxAliasesRule struct {
BaseRule
// contains filtered or unexported fields
}
MaxAliasesRule validates number of aliases
func (*MaxAliasesRule) Validate ¶ added in v1.1.1
func (r *MaxAliasesRule) Validate(ctx *ValidationContext) error
type MaxComplexityRule ¶ added in v1.1.1
type MaxComplexityRule struct {
BaseRule
// contains filtered or unexported fields
}
MaxComplexityRule validates query complexity
func (*MaxComplexityRule) Validate ¶ added in v1.1.1
func (r *MaxComplexityRule) Validate(ctx *ValidationContext) error
type MaxDepthRule ¶ added in v1.1.1
type MaxDepthRule struct {
BaseRule
// contains filtered or unexported fields
}
MaxDepthRule validates maximum query depth
func (*MaxDepthRule) Validate ¶ added in v1.1.1
func (r *MaxDepthRule) Validate(ctx *ValidationContext) error
type MaxTokensRule ¶ added in v1.1.1
type MaxTokensRule struct {
BaseRule
// contains filtered or unexported fields
}
MaxTokensRule limits query size by token count
func (*MaxTokensRule) Validate ¶ added in v1.1.1
func (r *MaxTokensRule) Validate(ctx *ValidationContext) error
type Message ¶ added in v1.1.0
type Message struct {
// Topic is the channel/topic name where this message was published
Topic string
// Data is the JSON-encoded payload
Data []byte
}
Message represents a published message with its topic and data payload.
type MultiValidationError ¶ added in v1.1.1
type MultiValidationError struct {
Errors []error
}
MultiValidationError combines multiple validation errors
func NewMultiValidationError ¶ added in v1.1.1
func NewMultiValidationError(errors []error) *MultiValidationError
func (*MultiValidationError) Error ¶ added in v1.1.1
func (e *MultiValidationError) Error() string
type MutationField ¶
type MutationField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the field name used in the GraphQL schema
Name() string
}
MutationField represents a GraphQL mutation field with its configuration. Implementations must provide both the field configuration and its name.
Use NewResolver to create MutationField instances:
mutation := graph.NewResolver[User]("createUser").
WithInputObject(CreateUserInput{}).
WithResolver(...).
BuildMutation()
type NoIntrospectionRule ¶ added in v1.1.1
type NoIntrospectionRule struct {
BaseRule
}
NoIntrospectionRule blocks introspection queries
func (*NoIntrospectionRule) Validate ¶ added in v1.1.1
func (r *NoIntrospectionRule) Validate(ctx *ValidationContext) error
type PageInfo ¶
type PageInfo struct {
HasNextPage bool `json:"hasNextPage" description:"Whether there are more pages"`
HasPreviousPage bool `json:"hasPreviousPage" description:"Whether there are previous pages"`
StartCursor string `json:"startCursor" description:"Cursor for the first item"`
EndCursor string `json:"endCursor" description:"Cursor for the last item"`
}
PageInfo contains pagination information
type PaginatedResponse ¶
type PaginatedResponse[T any] struct { Items []T `json:"items" description:"List of items"` TotalCount int `json:"totalCount" description:"Total number of items"` PageInfo PageInfo `json:"pageInfo" description:"Pagination information"` }
PaginatedResponse represents a paginated response structure
type PaginationArgs ¶
type PaginationArgs struct {
First *int `json:"first" description:"Number of items to fetch"`
After *string `json:"after" description:"Cursor to start after"`
Last *int `json:"last" description:"Number of items to fetch from end"`
Before *string `json:"before" description:"Cursor to start before"`
}
PaginationArgs contains pagination arguments
type PermissionRule ¶ added in v1.1.1
type PermissionRule struct {
BaseRule
// contains filtered or unexported fields
}
PermissionRule validates a single field requires specific permissions
func (*PermissionRule) Validate ¶ added in v1.1.1
func (r *PermissionRule) Validate(ctx *ValidationContext) error
type PermissionRules ¶ added in v1.1.1
type PermissionRules struct {
BaseRule
// contains filtered or unexported fields
}
PermissionRules validates multiple fields with permission requirements
func (*PermissionRules) Validate ¶ added in v1.1.1
func (r *PermissionRules) Validate(ctx *ValidationContext) error
type PubSub ¶ added in v1.1.0
type PubSub interface {
// Publish sends data to all subscribers of a topic.
// The data will be JSON-marshaled automatically.
//
// Returns an error if:
// - JSON marshaling fails
// - Context is canceled
// - PubSub is closed
Publish(ctx context.Context, topic string, data interface{}) error
// Subscribe creates a new subscription to a topic.
// Returns a channel that receives messages published to the topic.
//
// The subscription remains active until:
// - The context is canceled
// - Unsubscribe is called with the subscription ID
// - The PubSub is closed
//
// The returned channel will be closed when the subscription ends.
Subscribe(ctx context.Context, topic string) <-chan *Message
// Unsubscribe removes a subscription by its ID.
// The subscription's message channel will be closed.
Unsubscribe(ctx context.Context, subscriptionID string) error
// Close shuts down the PubSub system and closes all active subscriptions.
Close() error
}
PubSub defines the interface for publishing and subscribing to events. Implementations can use in-memory channels, Redis, Kafka, or other message brokers.
Example Usage:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
// Subscribe to a topic
ctx := context.Background()
subscription := pubsub.Subscribe(ctx, "messages:channel1")
// Publish an event
pubsub.Publish(ctx, "messages:channel1", map[string]string{"text": "Hello"})
// Receive events
for msg := range subscription {
fmt.Println("Received:", string(msg.Data))
}
type QueryField ¶
type QueryField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the field name used in the GraphQL schema
Name() string
}
QueryField represents a GraphQL query field with its configuration. Implementations must provide both the field configuration and its name.
Use NewResolver to create QueryField instances:
query := graph.NewResolver[User]("user").
WithArgs(...).
WithResolver(...).
BuildQuery()
type RateLimitOption ¶ added in v1.1.1
type RateLimitOption func(*RateLimitRule)
RateLimitOption configures rate limiting behavior
func WithBudgetFunc ¶ added in v1.1.1
func WithBudgetFunc(fn func(userID string) (int, error)) RateLimitOption
WithBudgetFunc sets the function to get user's remaining budget
func WithBypassRoles ¶ added in v1.1.1
func WithBypassRoles(roles ...string) RateLimitOption
WithBypassRoles sets roles that bypass rate limiting (e.g., "admin", "service")
func WithCostPerUnit ¶ added in v1.1.1
func WithCostPerUnit(cost int) RateLimitOption
WithCostPerUnit sets the cost multiplier per complexity unit (default: 1)
type RateLimitRule ¶ added in v1.1.1
type RateLimitRule struct {
BaseRule
// contains filtered or unexported fields
}
RateLimitRule implements per-user rate limiting based on query complexity
func (*RateLimitRule) Validate ¶ added in v1.1.1
func (r *RateLimitRule) Validate(ctx *ValidationContext) error
type RequireAuthRule ¶ added in v1.1.1
type RequireAuthRule struct {
BaseRule
// contains filtered or unexported fields
}
RequireAuthRule requires authentication for specific operations or fields Simply checks if ctx.UserDetails != nil
func (*RequireAuthRule) Validate ¶ added in v1.1.1
func (r *RequireAuthRule) Validate(ctx *ValidationContext) error
type ResolveParams ¶
type ResolveParams graphql.ResolveParams
type RoleRule ¶ added in v1.1.1
type RoleRule struct {
BaseRule
// contains filtered or unexported fields
}
RoleRule validates a single field requires specific roles
func (*RoleRule) Validate ¶ added in v1.1.1
func (r *RoleRule) Validate(ctx *ValidationContext) error
type RoleRules ¶ added in v1.1.1
type RoleRules struct {
BaseRule
// contains filtered or unexported fields
}
RoleRules validates multiple fields with role requirements
func (*RoleRules) Validate ¶ added in v1.1.1
func (r *RoleRules) Validate(ctx *ValidationContext) error
type RootInfo ¶ added in v1.2.2
type RootInfo map[string]interface{}
RootInfo wraps map[string]interface{} to implement RootInfoGetter. This allows using p.Info.RootValue with type-safe generic functions.
Usage:
// Extract user details from root value user := graph.GetRoot[UserDetails](graph.NewRootInfo(p), "details") // With error handling user, err := graph.GetRootE[UserDetails](graph.NewRootInfo(p), "details") // With default value token := graph.GetRootOr[string](graph.NewRootInfo(p), "token", "")
func NewRootInfo ¶ added in v1.2.2
func NewRootInfo(p ResolveParams) RootInfo
NewRootInfo extracts RootInfo from ResolveParams. Returns nil if RootValue is nil or not a map[string]interface{}.
Usage:
rootInfo := graph.NewRootInfo(p) user := graph.GetRoot[UserDetails](rootInfo, "details")
func (RootInfo) GetRootValue ¶ added in v1.2.2
GetRootValue implements RootInfoGetter interface
type RootInfoGetter ¶ added in v1.2.2
RootInfoGetter is an interface for types that can provide root value data by name. This allows GetRoot[T], GetRootE[T], GetRootOr[T], and MustGetRoot[T] to work with root value data extracted from ResolveParams.
type SchemaBuilder ¶
type SchemaBuilder struct {
// contains filtered or unexported fields
}
SchemaBuilder builds GraphQL schemas from QueryFields and MutationFields. Use NewSchemaBuilder to create an instance and Build() to generate the schema.
func NewSchemaBuilder ¶
func NewSchemaBuilder(params SchemaBuilderParams) *SchemaBuilder
NewSchemaBuilder creates a new schema builder with the provided query and mutation fields.
Example:
params := graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
MutationFields: []graph.MutationField{createUserMutation()},
}
builder := graph.NewSchemaBuilder(params)
schema, err := builder.Build()
func (*SchemaBuilder) Build ¶
func (sb *SchemaBuilder) Build() (graphql.Schema, error)
Build constructs and returns a graphql.Schema from the configured fields. It creates Query and Mutation root types based on the provided fields.
Returns an error if:
- Schema construction fails due to type conflicts
- Field configurations are invalid
The schema can have:
- Only queries (no mutations)
- Only mutations (no queries)
- Both queries and mutations
- Neither (empty schema)
type SchemaBuilderParams ¶
type SchemaBuilderParams struct {
// QueryFields: List of query fields to include in the schema
QueryFields []QueryField `group:"query_fields"`
// MutationFields: List of mutation fields to include in the schema
MutationFields []MutationField `group:"mutation_fields"`
// SubscriptionFields: List of subscription fields to include in the schema
// Requires WebSocket support and PubSub configuration
SubscriptionFields []SubscriptionField `group:"subscription_fields"`
}
SchemaBuilderParams configures the fields for building a GraphQL schema. Use this with NewSchemaBuilder to construct schemas using the builder pattern.
Example:
params := graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{
getUserQuery(),
listUsersQuery(),
},
MutationFields: []graph.MutationField{
createUserMutation(),
updateUserMutation(),
},
}
schema, err := graph.NewSchemaBuilder(params).Build()
type SubscriptionField ¶ added in v1.1.0
type SubscriptionField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the subscription field name
Name() string
}
SubscriptionField represents a GraphQL subscription field that can be added to a schema. It follows the same interface pattern as QueryField and MutationField.
Create subscription fields using NewSubscription:
sub := NewSubscription[MessageEvent]("messageAdded").
WithArgs(...).
WithResolver(...).
BuildSubscription()
type SubscriptionFilterFn ¶ added in v1.1.0
type SubscriptionFilterFn[T any] func(ctx context.Context, data *T, p ResolveParams) bool
SubscriptionFilterFn filters events before sending them to clients. Return true to send the event, false to skip it.
This is useful for:
- User-specific filtering based on permissions
- Content-based filtering based on subscription arguments
- Rate limiting or throttling
Example:
func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
userID, _ := GetRootString(p, "userID")
return canUserViewMessage(userID, data.ID)
}
type SubscriptionResolveFn ¶ added in v1.1.0
type SubscriptionResolveFn[T any] func(ctx context.Context, p ResolveParams) (<-chan *T, error)
SubscriptionResolveFn is the resolver function for subscriptions. It returns a channel that emits events of type T.
The resolver should:
- Create a buffered channel to prevent blocking
- Start a goroutine to handle event publishing
- Close the channel when done or context is canceled
- Handle errors by returning nil channel and an error
Example:
func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent, 10)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
if err := json.Unmarshal(msg.Data, &event); err == nil {
events <- &event
}
}
}()
return events, nil
}
type SubscriptionResolver ¶ added in v1.1.0
type SubscriptionResolver[T any] struct { // contains filtered or unexported fields }
SubscriptionResolver builds type-safe subscription fields with extensive customization capabilities. It provides a fluent API similar to UnifiedResolver for building subscriptions.
Type Parameters:
- T: The Go struct type that will be sent to subscribers
The resolver function should return a channel that emits events of type T. When the channel is closed or the context is canceled, the subscription ends.
Basic Usage:
type MessageEvent struct {
ID string `json:"id"`
Content string `json:"content"`
Timestamp time.Time `json:"timestamp"`
}
sub := NewSubscription[MessageEvent]("messageAdded").
WithDescription("Subscribe to new messages").
WithArgs(graphql.FieldConfigArgument{
"channelID": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
}).
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
json.Unmarshal(msg.Data, &event)
events <- &event
}
}()
return events, nil
}).
WithFilter(func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
// Optional: filter events before sending to client
return true
}).
WithMiddleware(AuthMiddleware("user")).
BuildSubscription()
Advanced Features:
// Middleware support
sub := NewSubscription[Event]("events").
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("admin")).
// ... rest of configuration
// Field-level customization (like UnifiedResolver)
sub := NewSubscription[ComplexEvent]("complexEvent").
WithFieldResolver("computedField", func(p ResolveParams) (interface{}, error) {
event := p.Source.(ComplexEvent)
return computeValue(event), nil
}).
// ... rest of configuration
func NewSubscription ¶ added in v1.1.0
func NewSubscription[T any](name string) *SubscriptionResolver[T]
NewSubscription creates a new subscription resolver with the specified name. The type parameter T determines the event type that will be sent to subscribers.
Example:
type UserStatusEvent struct {
UserID string `json:"userID"`
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
}
sub := NewSubscription[UserStatusEvent]("userStatusChanged").
WithArgs(graphql.FieldConfigArgument{
"userID": &graphql.ArgumentConfig{Type: graphql.String},
}).
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *UserStatusEvent, error) {
// Implementation
}).
BuildSubscription()
func (*SubscriptionResolver[T]) BuildSubscription ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) BuildSubscription() SubscriptionField
BuildSubscription builds and returns a SubscriptionField that can be added to the schema.
This method:
- Auto-generates the GraphQL type from the Go struct T
- Applies field-level customizations and middleware
- Creates the subscription and resolve functions
- Registers the type in the global type registry
Example:
sub := NewSubscription[MessageEvent]("messageAdded").
WithArgs(...).
WithResolver(...).
BuildSubscription()
// Add to schema
schema := graph.NewSchemaBuilder(graph.SchemaBuilderParams{
SubscriptionFields: []graph.SubscriptionField{sub},
})
func (*SubscriptionResolver[T]) WithArgs ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithArgs(args graphql.FieldConfigArgument) *SubscriptionResolver[T]
WithArgs sets custom arguments for the subscription.
Example:
WithArgs(graphql.FieldConfigArgument{
"channelID": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "Channel to subscribe to",
},
"filter": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "Optional filter pattern",
},
})
func (*SubscriptionResolver[T]) WithDescription ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithDescription(desc string) *SubscriptionResolver[T]
WithDescription adds a description to the subscription field.
func (*SubscriptionResolver[T]) WithFieldMiddleware ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *SubscriptionResolver[T]
WithFieldMiddleware adds middleware to a specific field in the event type.
Example:
WithFieldMiddleware("sensitiveData", AuthMiddleware("admin"))
func (*SubscriptionResolver[T]) WithFieldResolver ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *SubscriptionResolver[T]
WithFieldResolver overrides the resolver for a specific field in the event type. This allows customizing how specific fields are resolved.
Example:
WithFieldResolver("author", func(p ResolveParams) (interface{}, error) {
event := p.Source.(MessageEvent)
return userService.GetByID(event.AuthorID), nil
})
func (*SubscriptionResolver[T]) WithFilter ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFilter(fn SubscriptionFilterFn[T]) *SubscriptionResolver[T]
WithFilter adds a filter function to filter events before sending to clients. Only events that pass the filter (return true) will be sent.
Example:
WithFilter(func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
userID, _ := GetRootString(p, "userID")
return data.AuthorID != userID // Don't send user's own messages
})
func (*SubscriptionResolver[T]) WithMiddleware ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithMiddleware(middleware FieldMiddleware) *SubscriptionResolver[T]
WithMiddleware adds middleware to the subscription resolver. Middleware is executed in the order it's added.
Example:
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("user"))
func (*SubscriptionResolver[T]) WithResolver ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithResolver(fn SubscriptionResolveFn[T]) *SubscriptionResolver[T]
WithResolver sets the subscription resolver function. The resolver should return a channel that emits events of type T.
Example:
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent, 10)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
if err := json.Unmarshal(msg.Data, &event); err == nil {
events <- &event
}
}
}()
return events, nil
})
type TypedArgsResolver
deprecated
TypedArgsResolver provides type-safe argument handling
Deprecated: Use NewResolver with WithArg instead. The WithArg API provides a more flexible and chainable approach to defining arguments.
Migration example:
// Before (deprecated):
NewArgsResolver[User, GetUserArgs]("user").
WithResolver(func(ctx context.Context, p ResolveParams, args GetUserArgs) (*User, error) {
return userService.GetByID(args.ID)
}).BuildQuery()
// After (recommended):
NewResolver[User]("user").
WithArg("id", graph.Int).
WithResolver(func(p ResolveParams, args Args) (*User, error) {
id := Get[int](args, "id")
return userService.GetByID(id)
}).BuildQuery()
func NewArgsResolver
deprecated
func NewArgsResolver[T any, A any](name string, argName ...string) *TypedArgsResolver[T, A]
NewArgsResolver creates a resolver with type-safe arguments.
Deprecated: Use NewResolver with WithArg instead for a more flexible API.
Migration guide:
// Before (deprecated):
type GetUserArgs struct {
ID int `graphql:"id,required"`
}
NewArgsResolver[User, GetUserArgs]("user").
WithResolver(func(ctx context.Context, p ResolveParams, args GetUserArgs) (*User, error) {
return userService.GetByID(args.ID)
}).BuildQuery()
// After (recommended):
NewResolver[User]("user").
WithArgRequired("id", graph.Int).
WithResolver(func(p ResolveParams, args Args) (*User, error) {
id := Get[int](args, "id")
return userService.GetByID(id)
}).BuildQuery()
For struct inputs:
// Before (deprecated):
NewArgsResolver[User, CreateUserInput]("createUser").
WithResolver(func(ctx context.Context, p ResolveParams, input CreateUserInput) (*User, error) {
return userService.Create(input.Name, input.Email)
}).BuildMutation()
// After (recommended):
NewResolver[User]("createUser").
WithArg("input", CreateUserInput{}).
WithResolver(func(p ResolveParams, args Args) (*User, error) {
input := Get[CreateUserInput](args, "input")
return userService.Create(input.Name, input.Email)
}).BuildMutation()
func (*TypedArgsResolver[T, A]) AsList ¶
func (r *TypedArgsResolver[T, A]) AsList() *TypedArgsResolver[T, A]
AsList configures the resolver to return a list of items
func (*TypedArgsResolver[T, A]) AsPaginated ¶
func (r *TypedArgsResolver[T, A]) AsPaginated() *TypedArgsResolver[T, A]
AsPaginated configures the resolver to return paginated results
func (*TypedArgsResolver[T, A]) BuildMutation ¶
func (r *TypedArgsResolver[T, A]) BuildMutation() MutationField
BuildMutation builds and returns a MutationField
func (*TypedArgsResolver[T, A]) BuildQuery ¶
func (r *TypedArgsResolver[T, A]) BuildQuery() QueryField
BuildQuery builds and returns a QueryField
func (*TypedArgsResolver[T, A]) WithDescription ¶
func (r *TypedArgsResolver[T, A]) WithDescription(desc string) *TypedArgsResolver[T, A]
WithDescription sets the field description
func (*TypedArgsResolver[T, A]) WithResolver ¶
func (r *TypedArgsResolver[T, A]) WithResolver(resolver func(ctx context.Context, p ResolveParams, args A) (*T, error)) *TypedArgsResolver[T, A]
WithResolver sets a type-safe resolver with typed arguments and context support
Example usage:
type GetPostArgs struct {
ID int `graphql:"id,required"`
}
resolver.WithArgs[GetPostArgs]().
WithResolver(func(ctx context.Context, args GetPostArgs) (*Post, error) {
return postService.GetByID(args.ID)
})
type UnifiedResolver ¶
type UnifiedResolver[T any] struct { // contains filtered or unexported fields }
UnifiedResolver handles all GraphQL resolver scenarios with field-level customization
func NewResolver ¶
func NewResolver[T any](name string) *UnifiedResolver[T]
func (*UnifiedResolver[T]) AsList ¶
func (r *UnifiedResolver[T]) AsList() *UnifiedResolver[T]
Query Configuration
func (*UnifiedResolver[T]) AsMutation ¶
func (r *UnifiedResolver[T]) AsMutation() *UnifiedResolver[T]
Mutation Configuration
func (*UnifiedResolver[T]) AsPaginated ¶
func (r *UnifiedResolver[T]) AsPaginated() *UnifiedResolver[T]
func (*UnifiedResolver[T]) Build ¶
func (r *UnifiedResolver[T]) Build() interface{}
func (*UnifiedResolver[T]) BuildMutation ¶
func (r *UnifiedResolver[T]) BuildMutation() MutationField
func (*UnifiedResolver[T]) BuildQuery ¶
func (r *UnifiedResolver[T]) BuildQuery() QueryField
Build Methods
func (*UnifiedResolver[T]) Name ¶
func (r *UnifiedResolver[T]) Name() string
Interface Implementation
func (*UnifiedResolver[T]) Serve ¶
func (r *UnifiedResolver[T]) Serve() *graphql.Field
func (*UnifiedResolver[T]) WithArg ¶ added in v1.1.7
func (r *UnifiedResolver[T]) WithArg(name string, argType interface{}) *UnifiedResolver[T]
WithArg adds an argument to the resolver. Supports: - Go primitive types: string, int, int64, float64, bool (pass zero value or instance) - GraphQL types: graph.String, graph.Int, graph.Float, graph.Boolean, graph.ID - Struct types: any struct will be converted to GraphQL InputObject (supports deeply nested structs) - Slices: []Type will be converted to [Type] GraphQL list
Usage:
// With Go primitive types (recommended)
NewResolver[User]("user").
WithArg("id", ""). // string
WithArg("limit", 0). // int
WithArg("active", false). // bool
WithResolver(func(p ResolveParams) (*User, error) {
id := Get[string](ArgsMap(p.Args), "id")
return userService.GetByID(id)
})
// With struct type (deeply nested supported)
type AddressInput struct {
Street string `json:"street"`
City string `json:"city"`
}
type UserInput struct {
Name string `json:"name"`
Address AddressInput `json:"address"`
}
NewResolver[User]("createUser").
WithArg("input", UserInput{}).
WithResolver(func(p ResolveParams) (*User, error) {
input := Get[UserInput](ArgsMap(p.Args), "input")
return userService.Create(input)
})
func (*UnifiedResolver[T]) WithArgDefault ¶ added in v1.1.7
func (r *UnifiedResolver[T]) WithArgDefault(name string, argType interface{}, defaultValue interface{}) *UnifiedResolver[T]
WithArgDefault adds an argument with a default value
func (*UnifiedResolver[T]) WithArgRequired ¶ added in v1.1.7
func (r *UnifiedResolver[T]) WithArgRequired(name string, argType interface{}) *UnifiedResolver[T]
WithArgRequired adds a required argument to the resolver
func (*UnifiedResolver[T]) WithArgs ¶
func (r *UnifiedResolver[T]) WithArgs(args graphql.FieldConfigArgument) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithArgsFromStruct ¶
func (r *UnifiedResolver[T]) WithArgsFromStruct(structType interface{}) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithAsyncField ¶
func (r *UnifiedResolver[T]) WithAsyncField(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithCachedField ¶
func (r *UnifiedResolver[T]) WithCachedField(fieldName string, cacheKeyFunc func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithComputedField ¶
func (r *UnifiedResolver[T]) WithComputedField(name string, fieldType graphql.Output, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithCustomField ¶
func (r *UnifiedResolver[T]) WithCustomField(name string, field *graphql.Field) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithDescription ¶
func (r *UnifiedResolver[T]) WithDescription(desc string) *UnifiedResolver[T]
Basic Configuration
func (*UnifiedResolver[T]) WithFieldMiddleware ¶
func (r *UnifiedResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithFieldResolver ¶
func (r *UnifiedResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
Field-Level Customization
func (*UnifiedResolver[T]) WithFieldResolvers ¶
func (r *UnifiedResolver[T]) WithFieldResolvers(overrides map[string]graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObject ¶
func (r *UnifiedResolver[T]) WithInputObject(inputType interface{}) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObjectFieldName ¶
func (r *UnifiedResolver[T]) WithInputObjectFieldName(name string) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObjectNullable ¶
func (r *UnifiedResolver[T]) WithInputObjectNullable() *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithLazyField ¶
func (r *UnifiedResolver[T]) WithLazyField(fieldName string, loader func(interface{}) (interface{}, error)) *UnifiedResolver[T]
Utility Methods for Field Configuration
func (*UnifiedResolver[T]) WithMiddleware ¶
func (r *UnifiedResolver[T]) WithMiddleware(middleware FieldMiddleware) *UnifiedResolver[T]
WithMiddleware adds middleware to the main resolver. Middleware functions are applied in the order they are added (first added = outermost layer). This is the foundation for all resolver-level middleware (auth, logging, caching, etc.).
Example usage:
NewResolver[User]("user").
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("admin")).
WithResolver(func(p ResolveParams) (*User, error) {
return userService.GetByID(p.Args["id"].(int))
}).
BuildQuery()
func (*UnifiedResolver[T]) WithPermission ¶
func (r *UnifiedResolver[T]) WithPermission(middleware FieldMiddleware) *UnifiedResolver[T]
WithPermission adds permission middleware to the resolver (similar to Python @permission_classes decorator) This is now just a convenience wrapper around WithMiddleware for backwards compatibility
func (*UnifiedResolver[T]) WithResolver ¶
func (r *UnifiedResolver[T]) WithResolver(resolver func(ResolveParams) (*T, error)) *UnifiedResolver[T]
WithResolver sets a type-safe resolver function that returns *T instead of interface{} This provides better type safety and eliminates the need for type assertions or casts.
WithResolver requires the function signature:
- func(p ResolveParams) (*T, error)
Access arguments using type-safe getter functions with ArgsMap:
- Get[T](ArgsMap(p.Args), "key") - returns zero value if not found
- GetE[T](ArgsMap(p.Args), "key") - returns error if not found
- GetOr[T](ArgsMap(p.Args), "key", defaultVal) - returns default if not found
- MustGet[T](ArgsMap(p.Args), "key") - panics if not found
Example usage:
NewResolver[User]("user").
WithArg("id", graph.String).
WithResolver(func(p graph.ResolveParams) (*User, error) {
id := graph.Get[string](graph.ArgsMap(p.Args), "id")
return userService.GetByID(id)
}).BuildQuery()
NewResolver[User]("users").
AsList().
WithResolver(func(p graph.ResolveParams) (*[]User, error) {
users := userService.List()
return &users, nil
}).BuildQuery()
NewResolver[string]("hello").
WithResolver(func(p graph.ResolveParams) (*string, error) {
msg := "Hello, World!"
return &msg, nil
}).BuildQuery()
func (*UnifiedResolver[T]) WithTypedResolver ¶
func (r *UnifiedResolver[T]) WithTypedResolver(typedResolver interface{}) *UnifiedResolver[T]
Typed Resolver Support - allows direct struct parameters instead of graphql.ResolveParams
Example usage:
func resolveUser(args GetUserArgs) (*User, error) {
return &User{ID: args.ID, Name: "User"}, nil
}
NewResolver[User]("user", "User").
WithTypedResolver(resolveUser).
BuildQuery()
type ValidationContext ¶ added in v1.1.1
type ValidationContext struct {
// GraphQL query components
Query string
Document *ast.Document
Schema *graphql.Schema
Variables map[string]interface{}
// Request context
Request *http.Request
// User details from UserDetailsFn (can be nil if not authenticated)
// Validation rules can type-assert this to whatever structure they need
UserDetails interface{}
}
ValidationContext provides all necessary information for validation
type ValidationError ¶ added in v1.1.1
ValidationError provides detailed error information
func (*ValidationError) Error ¶ added in v1.1.1
func (e *ValidationError) Error() string
type ValidationOptions ¶ added in v1.1.1
type ValidationOptions struct {
// StopOnFirstError stops validation after first error
StopOnFirstError bool
// SkipInDebug skips validation when DEBUG=true
SkipInDebug bool
}
ValidationOptions configures validation behavior
type ValidationRule ¶ added in v1.1.1
type ValidationRule interface {
// Name returns a unique identifier for this rule
Name() string
// Validate executes the rule against the parsed query
// Returns nil if valid, error if validation fails
Validate(ctx *ValidationContext) error
// Enabled checks if this rule should be executed
Enabled() bool
// Enable enables the rule
Enable()
// Disable disables the rule
Disable()
}
ValidationRule represents a single validation rule that can be applied to GraphQL queries
func CombineRules ¶ added in v1.1.1
func CombineRules(ruleSets ...[]ValidationRule) []ValidationRule
CombineRules combines multiple rule sets into one
Example:
rules := CombineRules(
SecurityRules,
[]ValidationRule{NewRequireAuthRule("mutation")},
)
func DefaultValidationRules ¶ added in v1.1.1
func DefaultValidationRules() []ValidationRule
DefaultValidationRules returns the default validation rules Equivalent to EnableValidation=true
func DevelopmentValidationRules ¶ added in v1.1.1
func DevelopmentValidationRules() []ValidationRule
DevelopmentValidationRules returns lenient development rules
func NewBlockedFieldsRule ¶ added in v1.1.1
func NewBlockedFieldsRule(fields ...string) ValidationRule
NewBlockedFieldsRule creates a new blocked fields rule
Example:
NewBlockedFieldsRule("internalUsers", "deprecatedField")
func NewMaxAliasesRule ¶ added in v1.1.1
func NewMaxAliasesRule(maxAliases int) ValidationRule
NewMaxAliasesRule creates a new max aliases validation rule
func NewMaxComplexityRule ¶ added in v1.1.1
func NewMaxComplexityRule(maxComplexity int) ValidationRule
NewMaxComplexityRule creates a new max complexity validation rule
func NewMaxDepthRule ¶ added in v1.1.1
func NewMaxDepthRule(maxDepth int) ValidationRule
NewMaxDepthRule creates a new max depth validation rule
func NewMaxTokensRule ¶ added in v1.1.1
func NewMaxTokensRule(maxTokens int) ValidationRule
NewMaxTokensRule creates a new max tokens validation rule
func NewNoIntrospectionRule ¶ added in v1.1.1
func NewNoIntrospectionRule() ValidationRule
NewNoIntrospectionRule creates a new no introspection validation rule
func NewPermissionRule ¶ added in v1.1.1
func NewPermissionRule(field string, permissions ...string) ValidationRule
NewPermissionRule creates a new permission validation rule for a single field
Example:
NewPermissionRule("sensitiveData", "read:sensitive")
NewPermissionRule("exportData", "export:data", "admin:all")
func NewPermissionRules ¶ added in v1.1.1
func NewPermissionRules(config map[string][]string) ValidationRule
NewPermissionRules creates a batch permission validation rule config maps field names to required permissions
Example:
NewPermissionRules(map[string][]string{
"sensitiveData": {"read:sensitive"},
"exportData": {"export:data"},
"adminPanel": {"admin:access"},
})
func NewRateLimitRule ¶ added in v1.1.1
func NewRateLimitRule(opts ...RateLimitOption) ValidationRule
NewRateLimitRule creates a new rate limiting rule with optional configuration
Example:
NewRateLimitRule(
WithBudgetFunc(getBudgetFromRedis),
WithCostPerUnit(2),
WithBypassRoles("admin", "service"),
)
func NewRequireAuthRule ¶ added in v1.1.1
func NewRequireAuthRule(targets ...string) ValidationRule
NewRequireAuthRule creates a new require authentication rule Targets can be operation types ("mutation", "subscription", "query") or field names
Example:
NewRequireAuthRule("mutation", "subscription") // Require auth for all mutations and subscriptions
NewRequireAuthRule("deleteUser", "updateUser") // Require auth for specific fields
func NewRoleRule ¶ added in v1.1.1
func NewRoleRule(field string, roles ...string) ValidationRule
NewRoleRule creates a new role validation rule for a single field
Example:
NewRoleRule("deleteUser", "admin")
NewRoleRule("viewAuditLog", "admin", "auditor")
func NewRoleRules ¶ added in v1.1.1
func NewRoleRules(config map[string][]string) ValidationRule
NewRoleRules creates a batch role validation rule config maps field names to required roles
Example:
NewRoleRules(map[string][]string{
"deleteUser": {"admin"},
"viewAuditLog": {"admin", "auditor"},
"approveOrder": {"admin", "manager"},
})
func ProductionValidationRules ¶ added in v1.1.1
func ProductionValidationRules() []ValidationRule
ProductionValidationRules returns recommended production rules
type WSMessage ¶ added in v1.1.0
type WSMessage struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Payload map[string]interface{} `json:"payload,omitempty"`
}
WSMessage represents a GraphQL WebSocket Protocol message. This follows the graphql-ws protocol specification.
type WebSocketManager ¶ added in v1.1.0
type WebSocketManager struct {
// contains filtered or unexported fields
}
WebSocketManager manages WebSocket connections for GraphQL subscriptions. It handles connection lifecycle, authentication, and message routing.
func (*WebSocketManager) CloseAllConnections ¶ added in v1.1.0
func (m *WebSocketManager) CloseAllConnections()
CloseAllConnections closes all active WebSocket connections. This is useful for graceful shutdown.
func (*WebSocketManager) HandleWebSocket ¶ added in v1.1.0
func (m *WebSocketManager) HandleWebSocket(w http.ResponseWriter, r *http.Request)
HandleWebSocket upgrades HTTP connections to WebSocket and manages the connection lifecycle.
type WebSocketParams ¶ added in v1.1.0
type WebSocketParams struct {
// Schema: The GraphQL schema with subscription fields
Schema *graphql.Schema
// PubSub: The PubSub system for event distribution
PubSub PubSub
// CheckOrigin: Function to check WebSocket upgrade origin
// If nil, all origins are allowed (development only!)
CheckOrigin func(r *http.Request) bool
// AuthFn: Authentication function to extract user details from request
// Called during connection_init phase
AuthFn func(r *http.Request) (interface{}, error)
// RootObjectFn: Custom function to set up root object for each connection
// Similar to HTTP handler's RootObjectFn
RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
// PingInterval: Interval for sending ping messages (default: 30 seconds)
// Set to 0 to disable automatic pinging
PingInterval time.Duration
// ConnectionTimeout: Timeout for connection_init message (default: 10 seconds)
ConnectionTimeout time.Duration
}
WebSocketParams configures the WebSocket handler for subscriptions.
Source Files
¶
- doc.go
- graphql_generator.go
- graphql_schema_builder.go
- graphql_subscription_resolver.go
- graphql_unified_resolver.go
- graphql_validation.go
- graphql_validation_auth.go
- graphql_validation_example.go
- graphql_validation_presets.go
- graphql_validation_ratelimit.go
- graphql_validation_rules.go
- graphql_validation_security.go
- handler.go
- json.go
- pubsub.go
- time.go
- types.go
- utils.go
- websocket.go