1// Copyright 2015 Jean Niklas L'orange. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package edn implements encoding and decoding of EDN values as defined in 6// https://github.com/edn-format/edn. For a full introduction on how to use 7// go-edn, see https://github.com/go-edn/edn/blob/v1/docs/introduction.md. Fully 8// self-contained examples of go-edn can be found at 9// https://github.com/go-edn/edn/tree/v1/examples. 10// 11// Note that the small examples in this package is not checking errors as 12// persively as you should do when you use this package. This is done because 13// I'd like the examples to be easily readable and understandable. The bigger 14// examples provide proper error handling. 15package edn 16 17import ( 18 "encoding/base64" 19 "errors" 20 "math/big" 21 "reflect" 22 "sync" 23 "time" 24) 25 26var ( 27 ErrNotFunc = errors.New("Value is not a function") 28 ErrMismatchArities = errors.New("Function does not have single argument in, two argument out") 29 ErrNotConcrete = errors.New("Value is not a concrete non-function type") 30 ErrTagOverwritten = errors.New("Previous tag implementation was overwritten") 31) 32 33var globalTags TagMap 34 35// A TagMap contains mappings from tag literals to functions and structs that is 36// used when decoding. 37type TagMap struct { 38 sync.RWMutex 39 m map[string]reflect.Value 40} 41 42var errorType = reflect.TypeOf((*error)(nil)).Elem() 43 44// AddTagFn adds fn as a converter function for tagname tags to this TagMap. fn 45// must have the signature func(T) (U, error), where T is the expected input 46// type and U is the output type. See Decoder.AddTagFn for examples. 47func (tm *TagMap) AddTagFn(tagname string, fn interface{}) error { 48 // TODO: check name 49 rfn := reflect.ValueOf(fn) 50 rtyp := rfn.Type() 51 if rtyp.Kind() != reflect.Func { 52 return ErrNotFunc 53 } 54 if rtyp.NumIn() != 1 || rtyp.NumOut() != 2 || !rtyp.Out(1).Implements(errorType) { 55 // ok to have variadic arity? 56 return ErrMismatchArities 57 } 58 return tm.addVal(tagname, rfn) 59} 60 61func (tm *TagMap) addVal(name string, val reflect.Value) error { 62 tm.Lock() 63 if tm.m == nil { 64 tm.m = map[string]reflect.Value{} 65 } 66 _, ok := tm.m[name] 67 tm.m[name] = val 68 tm.Unlock() 69 if ok { 70 return ErrTagOverwritten 71 } else { 72 return nil 73 } 74} 75 76// AddTagFn adds fn as a converter function for tagname tags to the global 77// TagMap. fn must have the signature func(T) (U, error), where T is the 78// expected input type and U is the output type. See Decoder.AddTagFn for 79// examples. 80func AddTagFn(tagname string, fn interface{}) error { 81 return globalTags.AddTagFn(tagname, fn) 82} 83 84// AddTagStructs adds the struct as a matching struct for tagname tags to this 85// TagMap. val can not be a channel, function, interface or an unsafe pointer. 86// See Decoder.AddTagStruct for examples. 87func (tm *TagMap) AddTagStruct(tagname string, val interface{}) error { 88 rstruct := reflect.ValueOf(val) 89 switch rstruct.Type().Kind() { 90 case reflect.Invalid, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer: 91 return ErrNotConcrete 92 } 93 return tm.addVal(tagname, rstruct) 94} 95 96// AddTagStructs adds the struct as a matching struct for tagname tags to the 97// global TagMap. val can not be a channel, function, interface or an unsafe 98// pointer. See Decoder.AddTagStruct for examples. 99func AddTagStruct(tagname string, val interface{}) error { 100 return globalTags.AddTagStruct(tagname, val) 101} 102 103func init() { 104 err := AddTagFn("inst", func(s string) (time.Time, error) { 105 return time.Parse(time.RFC3339Nano, s) 106 }) 107 if err != nil { 108 panic(err) 109 } 110 err = AddTagFn("base64", base64.StdEncoding.DecodeString) 111 if err != nil { 112 panic(err) 113 } 114} 115 116// A MathContext specifies the precision and rounding mode for 117// `math/big.Float`s when decoding. 118type MathContext struct { 119 Precision uint 120 Mode big.RoundingMode 121} 122 123// The GlobalMathContext is the global MathContext. It is used if no other 124// context is provided. See MathContext for example usage. 125var GlobalMathContext = MathContext{ 126 Mode: big.ToNearestEven, 127 Precision: 192, 128} 129