PORTAL USER GUIDE
Let portal worry about trivial details, say goodbye to boilerplate code (our final goal)!
Options
Specify fields to keep: Only()
// keep field A only
c := New(Only("A"))
// keep field B and C of the nested struct A
c := New("A[B,C]")
Specify fields to exclude: Exclude()
// exclude field A
c := New(Exclude("A"))
// exclude field B and C of the nested struct A, but other fields of struct A are still selected.
c := New(Exclude("A[B,C]"))
Set custom tag for each field in runtime: CustomFieldTagMap()`.
It will override the default tag settings defined in your struct.
See example here.
Disable cache for a single dump
portal.Dump(&dst, &src, portal.DisableCache())
Special Tags
Load Data from Model’s Attribute: attr
// Model definition
type UserModel struct {
ID int
}
func (u *UserModel) Fullname() string {
return fmt.Sprintf("user:%d", u.ID)
}
// Fullname2 'attribute' method can accept an ctx param.
func (u *UserModel) Fullname2(ctx context.Context) string {
return fmt.Sprintf("user:%d", u.ID)
}
// Fullname3 'attribute' can return error too, portal will ignore the
// result if error returned.
func (u *UserModel) Fullname3(ctx context.Context) (string, error) {
return fmt.Sprintf("user:%d", u.ID)
}
type BadgeModel {
Name string
}
func (u *UserModel) Badge(ctx context.Context) (*BadgeModel, error) {
return &BadgeModel{
Name: "Cool"
}, nil
}
// Schema definition
type UserSchema struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty" portal:"attr:Fullname"`
// Chaining accessing is also supported.
// portal calls method `UserModel.Badge()`, then accesses Badge.Name field.
BadgeName string `json:"badge_name,omitempty" portal:"attr:Badge.Name"`
}
Load Data from Custom Method: meth
type TaskSchema struct {
Title string `json:"title,omitempty" portal:"meth:GetTitle"`
Description string `json:"description,omitempty" portal:"meth:GetDescription"`
// Chaining accessing is also supported for method result.
ScheduleAt *field.Timestamp `json:"schedule_at,omitempty" portal:"meth:FetchSchedule.At"`
ScheduleDescription *string `json:"schedule_description,omitempty" portal:"meth:FetchSchedule.Description"`
}
func (ts *TaskSchema) GetTitle(ctx context.Context, model *model.TaskModel) string {
// Accept extra context param.
// TODO: Read info from the `ctx` here.
return "Task Title"
}
func (ts *TaskSchema) GetDescription(model *model.TaskModel) (string, error) {
// Here we ignore the first context param.
// If method returns an error, portal will ignore the result.
return "Custom description", nil
}
type Schedule struct {
At time.Time
Description string
}
func (ts *TaskSchema) FetchSchedule(model *model.TaskModel) *Schedule {
return &Schedule{
Description: "High priority",
At: time.Now(),
}
}
Load Data Asynchronously: async
type TaskSchema struct {
Title string `json:"title,omitempty" portal:"meth:GetTitle;async"`
Description string `json:"description,omitempty" portal:"meth:GetDescription;async"`
}
Nested Schema: nested
type UserSchema struct {
ID string `json:"id,omitempty"`
}
type TaskSchema struct {
User *UserSchema `json:"user,omitempty" portal:"nested"``
}
Field Filtering: only & exclude
type NotiSchema struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
}
type UserSchema struct {
Notifications []*NotiSchema `json:"notifications,omitempty" portal:"nested;only:id,title"`
AnotherNotifications []*NotiSchema `json:"another_notifications,omitempty" portal:"nested;attr:Notifications;exclude:content"`
}
Set Const Value for Field: const
type UserSchema struct {
Type string `json:"type" portal:"const:vip"`
}
Disable Cache for Field: disablecache
type Student struct {
ID int
}
type info struct {
Name string
Height int
}
func(s *Student) Info() info {
return &info{Name: "name", Height: 180}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Info.Name,disablecache"`
Height int `json:"height" portal:"attr:Info.Height,disablecache"`
}
Set Default Value for Field: default
Only works for types: pointer/slice/map. For basic types (integer, string, bool), default value will be converted and set to field directly. For complex types (eg. map/slice/pointer to custom struct), set default to AUTO_INIT, portal will initialize field to its zero value.
type ContentSchema struct {
BizID *string `json:"biz_id" portal:"default:100"`
SkuID *string `json:"sku_id"` // -> json null
Users []*UserSchema `json:"users" portal:"default:AUTO_INIT"` // -> json []
Members map[string]int `json:"members" portal:"default:AUTO_INIT"` // -> json {}
User *UserSchema `json:"user" portal:"default:AUTO_INIT"`
}
Embedding Schema
type PersonSchema struct {
ID string `json:"id"`
Age int `json:"age"`
}
type UserSchema2 struct {
PersonSchema // embedded schema
Token string `json:"token"`
}
Custom Field Type
Custom field type must implements the Valuer and ValueSetter interface defined in types.go.
type Timestamp struct {
tm time.Time
}
func (t *Timestamp) SetValue(v interface{}) error {
switch timeValue := v.(type) {
case time.Time:
t.tm = timeValue
case *time.Time:
t.tm = *timeValue
default:
return fmt.Errorf("expect type `time.Time`, not `%T`", v)
}
return nil
}
func (t *Timestamp) Value() (interface{}, error) {
return t.tm, nil
}
func (t *Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal(t.tm.Unix())
}
func (t *Timestamp) UnmarshalJSON(data []byte) error {
var i int64
if err := json.Unmarshal(data, &i); err != nil {
return err
}
t.tm = time.Unix(i, 0)
return nil
}
Use Cache to speed up
Values from functions will be cached for schema fields tagged by ATTR and METH. You can choose not to use it by disabling the cache of a single field, a whole schema, or simple for one time Dump.
type StudentModel struct {
ID int
}
// the Meta might be costful
func (m *StudentModel) Meta() *meta {
time.Sleep(100 * time.Millisecond)
return &meta{ID: 1}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Meta.Name"`
Height int `json:"height" portal:"attr:Meta.Height"`
Subjects []*SubjectSchema `json:"subjects" portal:"nested;async"`
// NextID is a Set method, which must not be cached
NextID int `json:"next_id" portal:"meth:SetNextID;disablecache"` // no using cache
}
func (s *StudentSchema) SetNextID(m *StudentModel) int {
return m.ID + 1
}
type SubjectSchema struct {
Name string `json:"name" portal:"meth:GetInfo.Name"`
Teacher string `json:"teacher" portal:"meth:GetInfo.Teacher"`
}
// If you don't want SubjectSchema to use cache, forbidden it by implementing a PortalDisableCache method.
func (s *StudentSchema) PortalDisableCache() bool { return true }
func (s *StudentSchema) GetInfo(m *StudentModel) info {
return info{Name: "subject name", teacher: "teacher"}
}
var m = StudentModel{ID: 1}
var s StudentSchema
// setup cache
portal.SetCache(portal.DefaultCache)
defer portal.SetCache(nil)
// Not using cache for this dump
portal.Dump(&s, &m, portal.DisableCache())
// StudentSchema.NextID and whole SubjectSchema are not using cache
portal.Dump(&s, &m)
Incidently, portal.Cacher interface{} are expected to be implemented if you’d like to replace the portal.DefaultCache and to use your own.