Transformation
With Fuego, you can transform data coming in and out of your application. This is useful for a variety of reasons, such as:
- Converting data from one format to another
- Normalizing data
- Sanitizing data
- Masking sensitive data
- And more...
Input Transformation
Input transformation is the process of transforming the data coming into your application.
package main
import (
"context"
"strings"
"github.com/go-fuego/fuego"
)
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
func (u *User) InTransform(ctx context.Context) error {
u.FirstName = strings.ToUpper(u.FirstName)
u.LastName = strings.TrimSpace(u.LastName)
return nil
}
var _ fuego.InTransformer = (*User)(nil) // Ensure *User implements fuego.InTransformer
// This check is a classic example of Go's interface implementation check and we highly recommend to use it
In the example above, we have a User
struct with two fields: FirstName
and LastName
. We also have a method called InTransform
that takes a context.Context
and returns an error
. This method is called before the data is unmarshaled into the User
struct. In this method, we are transforming the FirstName
to uppercase and trimming any whitespace from the LastName
.
When using the following controller, the data will be transformed before it is unmarshaled into the User
struct.
func echoCapitalized(c fuego.ContextWithBody[User]) (User, error) {
user, err := c.Body()
if err != nil {
return User{}, err
}
// user.FirstName is in uppercase
return user, nil
}
Output Transformation
Output transformation is the process of transforming the data going out of your application before it's serialized and sent to the client.
package main
import (
"context"
"strings"
"github.com/go-fuego/fuego"
)
type UserResponse struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
FullName string `json:"full_name"`
}
func (u *UserResponse) OutTransform(ctx context.Context) error {
// Mask the last name for privacy
if len(u.LastName) > 0 {
u.LastName = u.LastName[:1] + "***"
}
// Generate the full name from first and last name
u.FullName = u.FirstName + " " + u.LastName
// Use the context to get the request ID
if reqID, ok := RequestIDFromContext(ctx); ok {
u.FullName += " (Request ID: " + reqID + ")"
}
return nil
}
var _ fuego.OutTransformer = (*UserResponse)(nil) // Ensure *UserResponse implements fuego.OutTransformer
In this example, the OutTransform
method is called before the UserResponse
struct is serialized and sent to the client. We're using it to generate a full name field and to mask the last name for privacy.
func getUser(c fuego.ContextNoBody) (UserResponse, error) {
// Get user from database or other source
user := UserResponse{
FirstName: "John",
LastName: "Doe",
}
// The OutTransform method will be called automatically before serialization
// After transformation, user.FullName will be "John Doe" and user.LastName will be "D***"
return user, nil
}
Custom Validation with Transformation
Transformation methods can also be used for custom validation that goes beyond what the standard validator can do:
func (u *User) InTransform(ctx context.Context) error {
// Normalize data
u.FirstName = strings.TrimSpace(u.FirstName)
u.LastName = strings.TrimSpace(u.LastName)
// Custom validation
if u.FirstName == "Admin" && u.LastName == "User" {
return errors.New("reserved name combination")
}
return nil
}
Recursion
Transformation is not recursive. If you have nested structs, you will need to transform each struct individually. This is done on purpose to give you more control over the transformation process: no assumptions are made about how you want to transform your data, no "magic".
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}
func (a *Address) InTransform(ctx context.Context) error {
a.Street = strings.TrimSpace(a.Street)
a.City = strings.ToUpper(a.City)
return nil
}
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Address Address `json:"address"` // Nested struct
}
func (u *User) InTransform(ctx context.Context) error {
u.FirstName = strings.ToUpper(u.FirstName)
u.LastName = strings.TrimSpace(u.LastName)
err := u.Address.InTransform(ctx) // Transform the nested struct
if err != nil {
return err
}
return nil
}
The same principle applies to OutTransform
for nested structs.
Transformation Flow
Here's the complete flow of data through Fuego's transformation and validation system:
- Request comes in with JSON/XML/etc. payload
- Payload is deserialized into your struct
InTransform
is called on your struct (if implemented)- Validation is performed on your struct (if validation tags are present)
- Your controller is called with the transformed and validated struct
- Your controller returns a response struct
OutTransform
is called on your response struct (if implemented)- Response struct is serialized to JSON/XML/etc. and sent to the client
This flow ensures that your data is properly transformed and validated at each step of the request/response cycle.