Skip to content

Latest commit

 

History

History
299 lines (237 loc) · 7.9 KB

USERGUIDE.md

File metadata and controls

299 lines (237 loc) · 7.9 KB

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.