github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/encoding.go (about)

     1  package goa
     2  
     3  import (
     4  	"encoding/gob"
     5  	"encoding/json"
     6  	"encoding/xml"
     7  	"fmt"
     8  	"io"
     9  	"mime"
    10  	"sync"
    11  	"time"
    12  )
    13  
    14  type (
    15  	// DecoderFunc instantiates a decoder that decodes data read from the given io reader.
    16  	DecoderFunc func(r io.Reader) Decoder
    17  
    18  	// A Decoder unmarshals an io.Reader into an interface.
    19  	Decoder interface {
    20  		Decode(v interface{}) error
    21  	}
    22  
    23  	// ResettableDecoder is used to determine whether or not a Decoder can be reset and thus
    24  	// safely reused in a sync.Pool.
    25  	ResettableDecoder interface {
    26  		Decoder
    27  		Reset(r io.Reader)
    28  	}
    29  
    30  	// decoderPool smartly determines whether to instantiate a new Decoder or reuse one from a
    31  	// sync.Pool.
    32  	decoderPool struct {
    33  		fn   DecoderFunc
    34  		pool *sync.Pool
    35  	}
    36  
    37  	// EncoderFunc instantiates an encoder that encodes data into the given writer.
    38  	EncoderFunc func(w io.Writer) Encoder
    39  
    40  	// An Encoder marshals from an interface into an io.Writer.
    41  	Encoder interface {
    42  		Encode(v interface{}) error
    43  	}
    44  
    45  	// The ResettableEncoder is used to determine whether or not a Encoder can be reset and
    46  	// thus safely reused in a sync.Pool.
    47  	ResettableEncoder interface {
    48  		Encoder
    49  		Reset(w io.Writer)
    50  	}
    51  
    52  	// encoderPool smartly determines whether to instantiate a new Encoder or reuse one from a
    53  	// sync.Pool.
    54  	encoderPool struct {
    55  		fn   EncoderFunc
    56  		pool *sync.Pool
    57  	}
    58  
    59  	// HTTPDecoder is a Decoder that decodes HTTP request or response bodies given a set of
    60  	// known Content-Type to decoder mapping.
    61  	HTTPDecoder struct {
    62  		pools map[string]*decoderPool // Registered decoders
    63  	}
    64  
    65  	// HTTPEncoder is a Encoder that encodes HTTP request or response bodies given a set of
    66  	// known Content-Type to encoder mapping.
    67  	HTTPEncoder struct {
    68  		pools        map[string]*encoderPool // Registered encoders
    69  		contentTypes []string                // List of content types for type negotiation
    70  	}
    71  )
    72  
    73  // NewJSONEncoder is an adapter for the encoding package JSON encoder.
    74  func NewJSONEncoder(w io.Writer) Encoder { return json.NewEncoder(w) }
    75  
    76  // NewJSONDecoder is an adapter for the encoding package JSON decoder.
    77  func NewJSONDecoder(r io.Reader) Decoder { return json.NewDecoder(r) }
    78  
    79  // NewXMLEncoder is an adapter for the encoding package XML encoder.
    80  func NewXMLEncoder(w io.Writer) Encoder { return xml.NewEncoder(w) }
    81  
    82  // NewXMLDecoder is an adapter for the encoding package XML decoder.
    83  func NewXMLDecoder(r io.Reader) Decoder { return xml.NewDecoder(r) }
    84  
    85  // NewGobEncoder is an adapter for the encoding package gob encoder.
    86  func NewGobEncoder(w io.Writer) Encoder { return gob.NewEncoder(w) }
    87  
    88  // NewGobDecoder is an adapter for the encoding package gob decoder.
    89  func NewGobDecoder(r io.Reader) Decoder { return gob.NewDecoder(r) }
    90  
    91  // NewHTTPEncoder creates an encoder that maps HTTP content types to low level encoders.
    92  func NewHTTPEncoder() *HTTPEncoder {
    93  	return &HTTPEncoder{
    94  		pools: make(map[string]*encoderPool),
    95  	}
    96  }
    97  
    98  // NewHTTPDecoder creates a decoder that maps HTTP content types to low level decoders.
    99  func NewHTTPDecoder() *HTTPDecoder {
   100  	return &HTTPDecoder{
   101  		pools: make(map[string]*decoderPool),
   102  	}
   103  }
   104  
   105  // Decode uses registered Decoders to unmarshal a body based on the contentType.
   106  func (decoder *HTTPDecoder) Decode(v interface{}, body io.Reader, contentType string) error {
   107  	now := time.Now()
   108  	defer MeasureSince([]string{"goa", "decode", contentType}, now)
   109  	var p *decoderPool
   110  	if contentType == "" {
   111  		// Default to JSON
   112  		contentType = "application/json"
   113  	} else {
   114  		if mediaType, _, err := mime.ParseMediaType(contentType); err == nil {
   115  			contentType = mediaType
   116  		}
   117  	}
   118  	p = decoder.pools[contentType]
   119  	if p == nil {
   120  		p = decoder.pools["*/*"]
   121  	}
   122  	if p == nil {
   123  		return nil
   124  	}
   125  
   126  	// the decoderPool will handle whether or not a pool is actually in use
   127  	d := p.Get(body)
   128  	defer p.Put(d)
   129  	if err := d.Decode(v); err != nil {
   130  		return err
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  // Register sets a specific decoder to be used for the specified content types. If a decoder is
   137  // already registered, it is overwritten.
   138  func (decoder *HTTPDecoder) Register(f DecoderFunc, contentTypes ...string) {
   139  	p := newDecodePool(f)
   140  
   141  	for _, contentType := range contentTypes {
   142  		mediaType, _, err := mime.ParseMediaType(contentType)
   143  		if err != nil {
   144  			mediaType = contentType
   145  		}
   146  		decoder.pools[mediaType] = p
   147  	}
   148  }
   149  
   150  // newDecodePool checks to see if the DecoderFunc returns reusable decoders and if so, creates a
   151  // pool.
   152  func newDecodePool(f DecoderFunc) *decoderPool {
   153  	// get a new decoder and type assert to see if it can be reset
   154  	d := f(nil)
   155  	rd, ok := d.(ResettableDecoder)
   156  
   157  	p := &decoderPool{fn: f}
   158  
   159  	// if the decoder can be reset, create a pool and put the typed decoder in
   160  	if ok {
   161  		p.pool = &sync.Pool{
   162  			New: func() interface{} { return f(nil) },
   163  		}
   164  		p.pool.Put(rd)
   165  	}
   166  
   167  	return p
   168  }
   169  
   170  // Get returns an already reset Decoder from the pool or creates a new one if necessary.
   171  func (p *decoderPool) Get(r io.Reader) Decoder {
   172  	if p.pool == nil {
   173  		return p.fn(r)
   174  	}
   175  
   176  	d := p.pool.Get().(ResettableDecoder)
   177  	d.Reset(r)
   178  	return d
   179  }
   180  
   181  // Put returns a Decoder into the pool if possible.
   182  func (p *decoderPool) Put(d Decoder) {
   183  	if p.pool == nil {
   184  		return
   185  	}
   186  	p.pool.Put(d)
   187  }
   188  
   189  // Encode uses the registered encoders and given content type to marshal and write the given value
   190  // using the given writer.
   191  func (encoder *HTTPEncoder) Encode(v interface{}, resp io.Writer, accept string) error {
   192  	now := time.Now()
   193  	if accept == "" {
   194  		accept = "*/*"
   195  	}
   196  	var contentType string
   197  	for _, t := range encoder.contentTypes {
   198  		if accept == "*/*" || accept == t {
   199  			contentType = accept
   200  			break
   201  		}
   202  	}
   203  	defer MeasureSince([]string{"goa", "encode", contentType}, now)
   204  	p := encoder.pools[contentType]
   205  	if p == nil && contentType != "*/*" {
   206  		p = encoder.pools["*/*"]
   207  	}
   208  	if p == nil {
   209  		return fmt.Errorf("No encoder registered for %s and no default encoder", contentType)
   210  	}
   211  
   212  	// the encoderPool will handle whether or not a pool is actually in use
   213  	e := p.Get(resp)
   214  	if err := e.Encode(v); err != nil {
   215  		return err
   216  	}
   217  	p.Put(e)
   218  
   219  	return nil
   220  }
   221  
   222  // Register sets a specific encoder to be used for the specified content types. If an encoder is
   223  // already registered, it is overwritten.
   224  func (encoder *HTTPEncoder) Register(f EncoderFunc, contentTypes ...string) {
   225  	p := newEncodePool(f)
   226  	for _, contentType := range contentTypes {
   227  		mediaType, _, err := mime.ParseMediaType(contentType)
   228  		if err != nil {
   229  			mediaType = contentType
   230  		}
   231  		encoder.pools[mediaType] = p
   232  	}
   233  
   234  	// Rebuild a unique index of registered content encoders to be used in EncodeResponse
   235  	encoder.contentTypes = make([]string, 0, len(encoder.pools))
   236  	for contentType := range encoder.pools {
   237  		encoder.contentTypes = append(encoder.contentTypes, contentType)
   238  	}
   239  }
   240  
   241  // newEncodePool checks to see if the EncoderFactory returns reusable encoders and if so, creates
   242  // a pool.
   243  func newEncodePool(f EncoderFunc) *encoderPool {
   244  	// get a new encoder and type assert to see if it can be reset
   245  	e := f(nil)
   246  	re, ok := e.(ResettableEncoder)
   247  
   248  	p := &encoderPool{fn: f}
   249  
   250  	// if the encoder can be reset, create a pool and put the typed encoder in
   251  	if ok {
   252  		p.pool = &sync.Pool{
   253  			New: func() interface{} { return f(nil) },
   254  		}
   255  		p.pool.Put(re)
   256  	}
   257  
   258  	return p
   259  }
   260  
   261  // Get returns an already reset Encoder from the pool or creates a new one if necessary.
   262  func (p *encoderPool) Get(w io.Writer) Encoder {
   263  	if p.pool == nil {
   264  		return p.fn(w)
   265  	}
   266  
   267  	e := p.pool.Get().(ResettableEncoder)
   268  	e.Reset(w)
   269  	return e
   270  }
   271  
   272  // Put returns a Decoder into the pool if possible.
   273  func (p *encoderPool) Put(e Encoder) {
   274  	if p.pool == nil {
   275  		return
   276  	}
   277  	p.pool.Put(e)
   278  }