-
Notifications
You must be signed in to change notification settings - Fork 11
/
edn_tags.go
142 lines (126 loc) · 4.52 KB
/
edn_tags.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
// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package edn implements encoding and decoding of EDN values as defined in
// https://github.com/edn-format/edn. For a full introduction on how to use
// go-edn, see https://github.com/go-edn/edn/blob/v1/docs/introduction.md. Fully
// self-contained examples of go-edn can be found at
// https://github.com/go-edn/edn/tree/v1/examples.
//
// Note that the small examples in this package is not checking errors as
// persively as you should do when you use this package. This is done because
// I'd like the examples to be easily readable and understandable. The bigger
// examples provide proper error handling.
package edn
import (
"encoding/base64"
"errors"
"math/big"
"reflect"
"sync"
"time"
)
var (
ErrNotFunc = errors.New("Value is not a function")
ErrMismatchArities = errors.New("Function does not have single argument in, two argument out")
ErrNotConcrete = errors.New("Value is not a concrete non-function type")
ErrTagOverwritten = errors.New("Previous tag implementation was overwritten")
)
var globalTags TagMap
// A TagMap contains mappings from tag literals to functions and structs that is
// used when decoding.
type TagMap struct {
sync.RWMutex
m map[string]reflect.Value
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// AddTagFn adds fn as a converter function for tagname tags to this TagMap. fn
// must have the signature func(T) (U, error), where T is the expected input
// type and U is the output type. See Decoder.AddTagFn for examples.
func (tm *TagMap) AddTagFn(tagname string, fn interface{}) error {
// TODO: check name
rfn := reflect.ValueOf(fn)
rtyp := rfn.Type()
if rtyp.Kind() != reflect.Func {
return ErrNotFunc
}
if rtyp.NumIn() != 1 || rtyp.NumOut() != 2 || !rtyp.Out(1).Implements(errorType) {
// ok to have variadic arity?
return ErrMismatchArities
}
return tm.addVal(tagname, rfn)
}
// MustAddTagFn adds fn as a converter function for tagname tags to this TagMap
// like AddTagFn, except this function panics if the tag could not be added.
func (tm *TagMap) MustAddTagFn(tagname string, fn interface{}) {
if err := tm.AddTagFn(tagname, fn); err != nil {
panic(err)
}
}
func (tm *TagMap) addVal(name string, val reflect.Value) error {
tm.Lock()
if tm.m == nil {
tm.m = map[string]reflect.Value{}
}
_, ok := tm.m[name]
tm.m[name] = val
tm.Unlock()
if ok {
return ErrTagOverwritten
} else {
return nil
}
}
// AddTagFn adds fn as a converter function for tagname tags to the global
// TagMap. fn must have the signature func(T) (U, error), where T is the
// expected input type and U is the output type. See Decoder.AddTagFn for
// examples.
func AddTagFn(tagname string, fn interface{}) error {
return globalTags.AddTagFn(tagname, fn)
}
// MustAddTagFn adds fn as a converter function for tagname tags to the global
// TagMap like AddTagFn, except this function panics if the tag could not be added.
func MustAddTagFn(tagname string, fn interface{}) {
globalTags.MustAddTagFn(tagname, fn)
}
// AddTagStructs adds the struct as a matching struct for tagname tags to this
// TagMap. val can not be a channel, function, interface or an unsafe pointer.
// See Decoder.AddTagStruct for examples.
func (tm *TagMap) AddTagStruct(tagname string, val interface{}) error {
rstruct := reflect.ValueOf(val)
switch rstruct.Type().Kind() {
case reflect.Invalid, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer:
return ErrNotConcrete
}
return tm.addVal(tagname, rstruct)
}
// AddTagStructs adds the struct as a matching struct for tagname tags to the
// global TagMap. val can not be a channel, function, interface or an unsafe
// pointer. See Decoder.AddTagStruct for examples.
func AddTagStruct(tagname string, val interface{}) error {
return globalTags.AddTagStruct(tagname, val)
}
func init() {
err := AddTagFn("inst", func(s string) (time.Time, error) {
return time.Parse(time.RFC3339Nano, s)
})
if err != nil {
panic(err)
}
err = AddTagFn("base64", base64.StdEncoding.DecodeString)
if err != nil {
panic(err)
}
}
// A MathContext specifies the precision and rounding mode for
// `math/big.Float`s when decoding.
type MathContext struct {
Precision uint
Mode big.RoundingMode
}
// The GlobalMathContext is the global MathContext. It is used if no other
// context is provided. See MathContext for example usage.
var GlobalMathContext = MathContext{
Mode: big.ToNearestEven,
Precision: 192,
}