package restful // Copyright 2015 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. import ( "encoding/xml" "strings" "sync" ) // EntityReaderWriter can read and write values using an encoding such as JSON,XML. type EntityReaderWriter interface { // Read a serialized version of the value from the request. // The Request may have a decompressing reader. Depends on Content-Encoding. Read(req *Request, v interface{}) error // Write a serialized version of the value on the response. // The Response may have a compressing writer. Depends on Accept-Encoding. // status should be a valid Http Status code Write(resp *Response, status int, v interface{}) error } // entityAccessRegistry is a singleton var entityAccessRegistry = &entityReaderWriters{ protection: new(sync.RWMutex), accessors: map[string]EntityReaderWriter{}, } // entityReaderWriters associates MIME to an EntityReaderWriter type entityReaderWriters struct { protection *sync.RWMutex accessors map[string]EntityReaderWriter } func init() { RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON)) RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML)) } // RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type. func RegisterEntityAccessor(mime string, erw EntityReaderWriter) { entityAccessRegistry.protection.Lock() defer entityAccessRegistry.protection.Unlock() entityAccessRegistry.accessors[mime] = erw } // NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content. // This package is already initialized with such an accessor using the MIME_JSON contentType. func NewEntityAccessorJSON(contentType string) EntityReaderWriter { return entityJSONAccess{ContentType: contentType} } // NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content. // This package is already initialized with such an accessor using the MIME_XML contentType. func NewEntityAccessorXML(contentType string) EntityReaderWriter { return entityXMLAccess{ContentType: contentType} } // accessorAt returns the registered ReaderWriter for this MIME type. func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) { r.protection.RLock() defer r.protection.RUnlock() er, ok := r.accessors[mime] if !ok { // retry with reverse lookup // more expensive but we are in an exceptional situation anyway for k, v := range r.accessors { if strings.Contains(mime, k) { return v, true } } } return er, ok } // entityXMLAccess is a EntityReaderWriter for XML encoding type entityXMLAccess struct { // This is used for setting the Content-Type header when writing ContentType string } // Read unmarshalls the value from XML func (e entityXMLAccess) Read(req *Request, v interface{}) error { return xml.NewDecoder(req.Request.Body).Decode(v) } // Write marshalls the value to JSON and set the Content-Type Header. func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error { return writeXML(resp, status, e.ContentType, v) } // writeXML marshalls the value to JSON and set the Content-Type Header. func writeXML(resp *Response, status int, contentType string, v interface{}) error { if v == nil { resp.WriteHeader(status) // do not write a nil representation return nil } if resp.prettyPrint { // pretty output must be created and written explicitly output, err := xml.MarshalIndent(v, " ", " ") if err != nil { return err } resp.Header().Set(HEADER_ContentType, contentType) resp.WriteHeader(status) _, err = resp.Write([]byte(xml.Header)) if err != nil { return err } _, err = resp.Write(output) return err } // not-so-pretty resp.Header().Set(HEADER_ContentType, contentType) resp.WriteHeader(status) return xml.NewEncoder(resp).Encode(v) } // entityJSONAccess is a EntityReaderWriter for JSON encoding type entityJSONAccess struct { // This is used for setting the Content-Type header when writing ContentType string } // Read unmarshalls the value from JSON func (e entityJSONAccess) Read(req *Request, v interface{}) error { decoder := NewDecoder(req.Request.Body) decoder.UseNumber() return decoder.Decode(v) } // Write marshalls the value to JSON and set the Content-Type Header. func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error { return writeJSON(resp, status, e.ContentType, v) } // write marshalls the value to JSON and set the Content-Type Header. func writeJSON(resp *Response, status int, contentType string, v interface{}) error { if v == nil { resp.WriteHeader(status) // do not write a nil representation return nil } if resp.prettyPrint { // pretty output must be created and written explicitly output, err := MarshalIndent(v, "", " ") if err != nil { return err } resp.Header().Set(HEADER_ContentType, contentType) resp.WriteHeader(status) _, err = resp.Write(output) return err } // not-so-pretty resp.Header().Set(HEADER_ContentType, contentType) resp.WriteHeader(status) return NewEncoder(resp).Encode(v) }