-
Notifications
You must be signed in to change notification settings - Fork 6
/
facade.go
161 lines (131 loc) · 5.47 KB
/
facade.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package codecs
import (
"errors"
"fmt"
"github.com/stretchr/objx"
"reflect"
)
const (
// facadeMaxRecursionLevel is the maximum number of recursions it will make before
// giving up and assuming circular recusion.
facadeMaxRecursionLevel int = 100
)
var (
// PublicDataDidNotFindMap is returned when the PublicData func fails to discover an appropriate
// public data object, which must end up being a map[string]interface{}.
PublicDataDidNotFindMap = errors.New("codecs: Object doesn't implement Facade interface and is not a Data object. PublicData(object) failed.")
// PublicDataTooMuchRecursion is returned when there is too much recursion when
// calling the Facade interfaces PublicData function. The PublicData must return either another
// object that implements Facade, or a map[string]interface{} that will be used for
// public data.
PublicDataTooMuchRecursion = errors.New("codecs: Facade object's PublicData() method caused too much recursion. Does one of your PublicData funcs return itself?")
)
// Facade is the interface objects should implement if they
// want to be responsible for providing an alternative data object
// to the codecs. Without this interface, the codecs will attempt to
// work on the object itself, whereas if an object implements this interface,
// the PublicData() method will be called instead, and the resulting object
// will instead be marshalled.
type Facade interface {
// PublicData should return an object containing the
// data to be marshalled. If the method returns an error, the codecs
// will send this error back to the calling code.
//
// The method may return either a final map[string]interface{} object,
// or else another object that implements the Facade interface.
//
// The PublicData method should return a new object, and not the original
// object, as it is possible that the response from PublicData will be modified
// before being used, and it is bad practice for these methods to alter the
// original data object.
PublicData(options map[string]interface{}) (publicData interface{}, err error)
}
// PublicData gets the data that is considered public for the specified object.
// If the object implements the Facade interface, its PublicData method is called
// until the returning object no longer implements the Facade interface at which point
// it is considered to have returned the public data.
//
// If the object passed in is an array or slice, PublicData is called on each object
// to build up an array of public versions of the objects, and an array will be
// returned.
//
// If the resulting object is not of the appropriate type, the PublicDataDidNotFindMap error will
// be returned.
//
// If one of the PublicData methods returns itself (or another object already in the path)
// thus resulting in too much recursion, the PublicDataTooMuchRecursion error is returned.
//
// If any of the objects' PublicData() method returns an error, that is directly returned.
func PublicData(object interface{}, options map[string]interface{}) (interface{}, error) {
return publicData(object, 0, options)
}
// PublicDataMap calls PublicData and returns the result after type asserting to objx.Map
func PublicDataMap(object interface{}, options map[string]interface{}) (objx.Map, error) {
data, err := publicData(object, 0, options)
if err != nil {
return nil, err
}
if data == nil {
return nil, nil
}
switch data.(type) {
case map[string]interface{}:
return objx.New(data.(map[string]interface{})), nil
case objx.Map:
return data.(objx.Map), nil
default:
if dataMap, ok := data.(objx.Map); ok {
return dataMap, nil
} else {
panic(fmt.Sprintf("codecs: PublicDataMap must refer to a map[string]interface{} or objx.Map, not %s. Did you mean to implement the Facade interface?", reflect.TypeOf(data)))
}
}
}
// publicData performs the work of PublicData keeping track of the level in order
// to ensure the code doesn't recurse too much.
func publicData(object interface{}, level int, options map[string]interface{}) (interface{}, error) {
// make sure we don't end up with too much recusrion
if level > facadeMaxRecursionLevel {
return nil, PublicDataTooMuchRecursion
}
// if object is nil, that's OK - we'll just return nil
if object == nil {
return nil, nil
}
// handle arrays and slices - https://github.com/stretchr/goweb/issues/27
objectValue := reflect.ValueOf(object)
objectKind := objectValue.Kind()
if objectKind == reflect.Array || objectKind == reflect.Slice {
// make an array to hold the items
length := objectValue.Len()
arr := make([]interface{}, length)
// get the public data for each item
for subObjIndex := 0; subObjIndex < length; subObjIndex++ {
// get this object
subObj := objectValue.Index(subObjIndex).Interface()
// ask for the object's public data
subPublic, subPublicErr := publicData(subObj, level+1, options)
// throw an error if there is one
if subPublicErr != nil {
return nil, subPublicErr
}
// add the item to the array
arr[subObjIndex] = subPublic
}
// return the object
return arr, nil
}
// cast the object
if objectWithFacade, ok := object.(Facade); ok {
publicObject, err := objectWithFacade.PublicData(options)
// return the public data error if there was one
if err != nil {
return nil, err
}
// recursivly call publicData until the object no longer
// implements the Facade interface.
return publicData(publicObject, level+1, options)
}
// we can't do anything - so just return the object back
return object, nil
}