Binders
Overview
Binders are responsible for extracting and binding data from HTTP requests (query parameters, form data, JSON bodies) into structured input objects for Maestro operations. They provide a clean interface between raw HTTP data and typed Go structures.
Purpose
Binders serve several key functions: - Data Extraction - Parse HTTP requests and extract relevant data - Type Conversion - Convert string values to appropriate Go types - Input Preparation - Populate Maestro input structures with bound data - Request Context - Handle request-specific context and metadata
Structure
Each binder typically follows this pattern:
type NewUserAppBinder struct {
Binder *binder.Binder
}
func (this *NewUserAppBinder) Bind(ctx context.Context, in *maes.NewUserMaeIn, c *gin.Context) error {
this.bindRequest(ctx, in, c)
return nil
}
Common Binding Methods
Query String Binding
func (this *SomeBinder) bindRequestForm(ctx context.Context, in *maes.SomeMaeIn, c *gin.Context) error {
s := &in.Request.Form
s.Name = this.Binder.QueryStringField("form[name]", c, &in.Context)
s.Email = this.Binder.QueryStringField("form[email]", c, &in.Context)
return nil
}
JSON Body Binding
func (this *SomeBinder) bindRequestJSON(ctx context.Context, in *maes.SomeMaeIn, c *gin.Context) error {
var jsonData struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := c.ShouldBindJSON(&jsonData); err != nil {
return err
}
in.Request.Form.Name = jsonData.Name
in.Request.Form.Email = jsonData.Email
return nil
}
Form Data Binding
func (this *SomeBinder) bindRequestForm(ctx context.Context, in *maes.SomeMaeIn, c *gin.Context) error {
s := &in.Request.Form
s.Name = c.PostForm("name")
s.Email = c.PostForm("email")
return nil
}
Integration with Maestros
Binders are typically called at the beginning of controller methods:
func (gc *GardenController) Create(c *gin.Context) {
input := &maes.CreateGardenMaeIn{}
// Bind request data
if err := gc.CreateGardenBinder.Bind(c.Request.Context(), input, c); err != nil {
c.JSON(400, gin.H{"error": "Invalid request data"})
return
}
// Execute business logic
output, err := gc.CreateGardenMae.Execute(c.Request.Context(), input)
// ...
}
Request Context Handling
Binders also populate request context information:
func (this *SomeBinder) bindContext(ctx context.Context, in *maes.SomeMaeIn, c *gin.Context) error {
// Extract user context
if userID, exists := c.Get("user_id"); exists {
in.Context.UserId = userID.(string)
}
// Set request metadata
in.Context.RequestID = c.GetString("request_id")
in.Context.Timestamp = time.Now()
return nil
}
Error Handling
Binders should handle binding errors gracefully:
func (this *SomeBinder) Bind(ctx context.Context, in *maes.SomeMaeIn, c *gin.Context) error {
if err := this.bindRequest(ctx, in, c); err != nil {
return fmt.Errorf("failed to bind request: %w", err)
}
if err := this.validateBindings(in); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
return nil
}
Best Practices
Type Safety
- Always convert string inputs to appropriate types
- Handle conversion errors gracefully
- Provide default values when appropriate
Validation
- Perform basic format validation during binding
- Leave business rule validation to validators
- Return clear error messages for binding failures
Performance
- Bind only necessary data
- Avoid expensive operations in binders
- Cache parsed values when appropriate
Security
- Sanitize input data
- Validate parameter names and values
- Prevent parameter pollution attacks
Testing Binders
func TestCreateGardenBinder(t *testing.T) {
binder := setupCreateGardenBinder()
// Setup mock request
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("POST", "/gardens", strings.NewReader(`{"name": "Test Garden"}`))
c.Request.Header.Set("Content-Type", "application/json")
input := &maes.CreateGardenMaeIn{}
err := binder.Bind(context.Background(), input, c)
assert.NoError(t, err)
assert.Equal(t, "Test Garden", input.Request.Form.Name)
}
Binders provide the crucial first step in request processing, ensuring that raw HTTP data is properly structured and typed for use throughout the application.