mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
New XMLMap func which makes a map value type an xml compatible content to render and give option to render a Problem as XML - rel to #1335
Former-commit-id: dcf21098ff7af6becfa9896df5f82c3b0b53f0ac
This commit is contained in:
parent
8d188817a6
commit
75ea978194
|
@ -87,6 +87,11 @@ func problemExample(ctx iris.Context) {
|
|||
JSON: iris.JSON{
|
||||
Indent: " ",
|
||||
},
|
||||
// OR
|
||||
// Render as XML:
|
||||
// RenderXML: true,
|
||||
// XML: iris.XML{Indent: " "},
|
||||
//
|
||||
// Sets the "Retry-After" response header.
|
||||
//
|
||||
// Can accept:
|
||||
|
|
|
@ -778,19 +778,23 @@ type Context interface {
|
|||
HTML(format string, args ...interface{}) (int, error)
|
||||
// JSON marshals the given interface object and writes the JSON response.
|
||||
JSON(v interface{}, options ...JSON) (int, error)
|
||||
// Problem writes a JSON problem response.
|
||||
// JSONP marshals the given interface object and writes the JSON response.
|
||||
JSONP(v interface{}, options ...JSONP) (int, error)
|
||||
// XML marshals the given interface object and writes the XML response.
|
||||
// To render maps as XML see the `XMLMap` package-level function.
|
||||
XML(v interface{}, options ...XML) (int, error)
|
||||
// Problem writes a JSON or XML problem response.
|
||||
// Order of Problem fields are not always rendered the same.
|
||||
//
|
||||
// Behaves exactly like `Context.JSON`
|
||||
// but with default ProblemOptions.JSON indent of " " and
|
||||
// a response content type of "application/problem+json" instead.
|
||||
//
|
||||
// Use the options.RenderXML and XML fields to change this behavior and
|
||||
// send a response of content type "application/problem+xml" instead.
|
||||
//
|
||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||
Problem(v interface{}, opts ...ProblemOptions) (int, error)
|
||||
// JSONP marshals the given interface object and writes the JSON response.
|
||||
JSONP(v interface{}, options ...JSONP) (int, error)
|
||||
// XML marshals the given interface object and writes the XML response.
|
||||
XML(v interface{}, options ...XML) (int, error)
|
||||
// Markdown parses the markdown to html and renders its result to the client.
|
||||
Markdown(markdownB []byte, options ...Markdown) (int, error)
|
||||
// YAML parses the "v" using the yaml parser and renders its result to the client.
|
||||
|
@ -3015,9 +3019,12 @@ const (
|
|||
ContentHTMLHeaderValue = "text/html"
|
||||
// ContentJSONHeaderValue header value for JSON data.
|
||||
ContentJSONHeaderValue = "application/json"
|
||||
// ContentJSONProblemHeaderValue header value for API problem error.
|
||||
// ContentJSONProblemHeaderValue header value for JSON API problem error.
|
||||
// Read more at: https://tools.ietf.org/html/rfc7807
|
||||
ContentJSONProblemHeaderValue = "application/problem+json"
|
||||
// ContentXMLProblemHeaderValue header value for XML API problem error.
|
||||
// Read more at: https://tools.ietf.org/html/rfc7807
|
||||
ContentXMLProblemHeaderValue = "application/problem+xml"
|
||||
// ContentJavascriptHeaderValue header value for JSONP & Javascript data.
|
||||
ContentJavascriptHeaderValue = "application/javascript"
|
||||
// ContentTextHeaderValue header value for Text data.
|
||||
|
@ -3187,35 +3194,6 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// Problem writes a JSON problem response.
|
||||
// Order of Problem fields are not always rendered the same.
|
||||
//
|
||||
// Behaves exactly like `Context.JSON`
|
||||
// but with default ProblemOptions.JSON indent of " " and
|
||||
// a response content type of "application/problem+json" instead.
|
||||
//
|
||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||
func (ctx *context) Problem(v interface{}, opts ...ProblemOptions) (int, error) {
|
||||
options := DefaultProblemOptions
|
||||
if len(opts) > 0 {
|
||||
options = opts[0]
|
||||
// Currently apply only if custom options passsed, otherwise,
|
||||
// with the current settings, it's not required.
|
||||
// This may change in the future though.
|
||||
options.Apply(ctx)
|
||||
}
|
||||
|
||||
if p, ok := v.(Problem); ok {
|
||||
p.updateTypeToAbsolute(ctx)
|
||||
code, _ := p.getStatus()
|
||||
ctx.StatusCode(code)
|
||||
}
|
||||
|
||||
ctx.contentTypeOnce(ContentJSONProblemHeaderValue, "")
|
||||
|
||||
return ctx.JSON(v, options.JSON)
|
||||
}
|
||||
|
||||
var (
|
||||
finishCallbackB = []byte(");")
|
||||
)
|
||||
|
@ -3279,6 +3257,46 @@ func (ctx *context) JSONP(v interface{}, opts ...JSONP) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
type xmlMapEntry struct {
|
||||
XMLName xml.Name
|
||||
Value interface{} `xml:",chardata"`
|
||||
}
|
||||
|
||||
// XMLMap wraps a map[string]interface{} to compatible xml marshaler,
|
||||
// in order to be able to render maps as XML on the `Context.XML` method.
|
||||
//
|
||||
// Example: `Context.XML(XMLMap("Root", map[string]interface{}{...})`.
|
||||
func XMLMap(elementName string, v Map) xml.Marshaler {
|
||||
return xmlMap{
|
||||
entries: v,
|
||||
elementName: elementName,
|
||||
}
|
||||
}
|
||||
|
||||
type xmlMap struct {
|
||||
entries Map
|
||||
elementName string
|
||||
}
|
||||
|
||||
// MarshalXML marshals a map to XML.
|
||||
func (m xmlMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if len(m.entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
start.Name = xml.Name{Local: m.elementName}
|
||||
err := e.EncodeToken(start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range m.entries {
|
||||
e.Encode(xmlMapEntry{XMLName: xml.Name{Local: k}, Value: v})
|
||||
}
|
||||
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
// WriteXML marshals the given interface object and writes the XML response to the writer.
|
||||
func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
|
||||
if prefix := options.Prefix; prefix != "" {
|
||||
|
@ -3306,6 +3324,7 @@ func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
|
|||
var DefaultXMLOptions = XML{}
|
||||
|
||||
// XML marshals the given interface object and writes the XML response to the client.
|
||||
// To render maps as XML see the `XMLMap` package-level function.
|
||||
func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
|
||||
options := DefaultXMLOptions
|
||||
|
||||
|
@ -3325,6 +3344,47 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// Problem writes a JSON or XML problem response.
|
||||
// Order of Problem fields are not always rendered the same.
|
||||
//
|
||||
// Behaves exactly like `Context.JSON`
|
||||
// but with default ProblemOptions.JSON indent of " " and
|
||||
// a response content type of "application/problem+json" instead.
|
||||
//
|
||||
// Use the options.RenderXML and XML fields to change this behavior and
|
||||
// send a response of content type "application/problem+xml" instead.
|
||||
//
|
||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||
func (ctx *context) Problem(v interface{}, opts ...ProblemOptions) (int, error) {
|
||||
options := DefaultProblemOptions
|
||||
if len(opts) > 0 {
|
||||
options = opts[0]
|
||||
// Currently apply only if custom options passsed, otherwise,
|
||||
// with the current settings, it's not required.
|
||||
// This may change in the future though.
|
||||
options.Apply(ctx)
|
||||
}
|
||||
|
||||
if p, ok := v.(Problem); ok {
|
||||
// if !p.Validate() {
|
||||
// ctx.StatusCode(http.StatusInternalServerError)
|
||||
// return ErrNotValidProblem
|
||||
// }
|
||||
p.updateURIsToAbs(ctx)
|
||||
code, _ := p.getStatus()
|
||||
ctx.StatusCode(code)
|
||||
|
||||
if options.RenderXML {
|
||||
ctx.contentTypeOnce(ContentXMLProblemHeaderValue, "")
|
||||
// Problem is an xml Marshaler already, don't use `XMLMap`.
|
||||
return ctx.XML(v, options.XML)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.contentTypeOnce(ContentJSONProblemHeaderValue, "")
|
||||
return ctx.JSON(v, options.JSON)
|
||||
}
|
||||
|
||||
// WriteMarkdown parses the markdown to html and writes these contents to the writer.
|
||||
func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, error) {
|
||||
buf := blackfriday.Run(markdownB)
|
||||
|
@ -3571,7 +3631,7 @@ func (ctx *context) Negotiate(v interface{}) (int, error) {
|
|||
return ctx.Markdown(v.([]byte))
|
||||
case ContentJSONHeaderValue:
|
||||
return ctx.JSON(v)
|
||||
case ContentJSONProblemHeaderValue:
|
||||
case ContentJSONProblemHeaderValue, ContentXMLProblemHeaderValue:
|
||||
return ctx.Problem(v)
|
||||
case ContentJavascriptHeaderValue:
|
||||
return ctx.JSONP(v)
|
||||
|
@ -3702,9 +3762,9 @@ func (n *NegotiationBuilder) JSON(v ...interface{}) *NegotiationBuilder {
|
|||
return n.MIME(ContentJSONHeaderValue, content)
|
||||
}
|
||||
|
||||
// Problem registers the "application/problem+json" content type and, optionally,
|
||||
// Problem registers the "application/problem+xml" or "application/problem+xml" content type and, optionally,
|
||||
// a value that `Context.Negotiate` will render
|
||||
// when a client accepts the "application/problem+json" content type.
|
||||
// when a client accepts the "application/problem+json" or the "application/problem+xml" content type.
|
||||
//
|
||||
// Returns itself for recursive calls.
|
||||
func (n *NegotiationBuilder) Problem(v ...interface{}) *NegotiationBuilder {
|
||||
|
@ -3712,7 +3772,7 @@ func (n *NegotiationBuilder) Problem(v ...interface{}) *NegotiationBuilder {
|
|||
if len(v) > 0 {
|
||||
content = v[0]
|
||||
}
|
||||
return n.MIME(ContentJSONProblemHeaderValue, content)
|
||||
return n.MIME(ContentJSONProblemHeaderValue+","+ContentXMLProblemHeaderValue, content)
|
||||
}
|
||||
|
||||
// JSONP registers the "application/javascript" content type and, optionally,
|
||||
|
@ -3968,10 +4028,11 @@ func (n *NegotiationAcceptBuilder) JSON() *NegotiationAcceptBuilder {
|
|||
return n.MIME(ContentJSONHeaderValue)
|
||||
}
|
||||
|
||||
// Problem adds the "application/problem+json" as accepted client content type.
|
||||
// Problem adds the "application/problem+json" and "application/problem-xml"
|
||||
// as accepted client content types.
|
||||
// Returns itself.
|
||||
func (n *NegotiationAcceptBuilder) Problem() *NegotiationAcceptBuilder {
|
||||
return n.MIME(ContentJSONProblemHeaderValue)
|
||||
return n.MIME(ContentJSONProblemHeaderValue, ContentXMLProblemHeaderValue)
|
||||
}
|
||||
|
||||
// JSONP adds the "application/javascript" as accepted client content type.
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -57,10 +59,10 @@ func isEmptyTypeURI(uri string) bool {
|
|||
return uri == "" || uri == "about:blank"
|
||||
}
|
||||
|
||||
func (p Problem) getType() string {
|
||||
typeField, found := p["type"]
|
||||
func (p Problem) getURI(key string) string {
|
||||
f, found := p[key]
|
||||
if found {
|
||||
if typ, ok := typeField.(string); ok {
|
||||
if typ, ok := f.(string); ok {
|
||||
if !isEmptyTypeURI(typ) {
|
||||
return typ
|
||||
}
|
||||
|
@ -71,18 +73,22 @@ func (p Problem) getType() string {
|
|||
}
|
||||
|
||||
// Updates "type" field to absolute URI, recursively.
|
||||
func (p Problem) updateTypeToAbsolute(ctx Context) {
|
||||
func (p Problem) updateURIsToAbs(ctx Context) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if uriRef := p.getType(); uriRef != "" {
|
||||
if uriRef := p.getURI("type"); uriRef != "" {
|
||||
p.Type(ctx.AbsoluteURI(uriRef))
|
||||
}
|
||||
|
||||
if uriRef := p.getURI("instance"); uriRef != "" {
|
||||
p.Instance(ctx.AbsoluteURI(uriRef))
|
||||
}
|
||||
|
||||
if cause, ok := p["cause"]; ok {
|
||||
if causeP, ok := cause.(Problem); ok {
|
||||
causeP.updateTypeToAbsolute(ctx)
|
||||
causeP.updateURIsToAbs(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +169,14 @@ func (p Problem) Detail(detail string) Problem {
|
|||
return p.Key("detail", detail)
|
||||
}
|
||||
|
||||
// Instance sets the problem's instance field.
|
||||
// A URI reference that identifies the specific
|
||||
// occurrence of the problem. It may or may not yield further
|
||||
// information if dereferenced.
|
||||
func (p Problem) Instance(instanceURI string) Problem {
|
||||
return p.Key("instance", instanceURI)
|
||||
}
|
||||
|
||||
// Cause sets the problem's cause field.
|
||||
// Any chain of problems.
|
||||
func (p Problem) Cause(cause Problem) Problem {
|
||||
|
@ -196,9 +210,29 @@ func (p Problem) Error() string {
|
|||
return fmt.Sprintf("[%d] %s", p["status"], p["title"])
|
||||
}
|
||||
|
||||
// MarshalXML makes this Problem XML-compatible content to render.
|
||||
func (p Problem) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := e.EncodeToken(start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range p {
|
||||
// convert keys like "type" to "Type", "productName" to "ProductName" and e.t.c. when xml.
|
||||
e.Encode(xmlMapEntry{XMLName: xml.Name{Local: strings.Title(k)}, Value: v})
|
||||
}
|
||||
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
// DefaultProblemOptions the default options for `Context.Problem` method.
|
||||
var DefaultProblemOptions = ProblemOptions{
|
||||
JSON: JSON{Indent: " "},
|
||||
XML: XML{Indent: " "},
|
||||
}
|
||||
|
||||
// ProblemOptions the optional settings when server replies with a Problem.
|
||||
|
@ -207,6 +241,13 @@ type ProblemOptions struct {
|
|||
// JSON are the optional JSON renderer options.
|
||||
JSON JSON
|
||||
|
||||
// RenderXML set to true if want to render as XML doc.
|
||||
// See `XML` option field too.
|
||||
RenderXML bool
|
||||
// XML are the optional XML renderer options.
|
||||
// Affect only when `RenderXML` field is set to true.
|
||||
XML XML
|
||||
|
||||
// RetryAfter sets the Retry-After response header.
|
||||
// https://tools.ietf.org/html/rfc7231#section-7.1.3
|
||||
// The value can be one of those:
|
||||
|
|
5
go19.go
5
go19.go
|
@ -67,7 +67,10 @@ type (
|
|||
//
|
||||
// It is an alias of the `context#JSON` type.
|
||||
JSON = context.JSON
|
||||
|
||||
// XML the optional settings for XML renderer.
|
||||
//
|
||||
// It is an alias of the `context#XML` type.
|
||||
XML = context.XML
|
||||
// Supervisor is a shortcut of the `host#Supervisor`.
|
||||
// Used to add supervisor configurators on common Runners
|
||||
// without the need of importing the `core/host` package.
|
||||
|
|
7
iris.go
7
iris.go
|
@ -503,6 +503,13 @@ var (
|
|||
//
|
||||
// A shortcut for the `context#NewProblem`.
|
||||
NewProblem = context.NewProblem
|
||||
// XMLMap wraps a map[string]interface{} to compatible xml marshaler,
|
||||
// in order to be able to render maps as XML on the `Context.XML` method.
|
||||
//
|
||||
// Example: `Context.XML(XMLMap("Root", map[string]interface{}{...})`.
|
||||
//
|
||||
// A shortcut for the `context#XMLMap`.
|
||||
XMLMap = context.XMLMap
|
||||
)
|
||||
|
||||
// Contains the enum values of the `Context.GetReferrer()` method,
|
||||
|
|
Loading…
Reference in New Issue
Block a user