feat: Add support for XML renderer (#198)

* Add support for XML renderer

* formatting

* update docs
This commit is contained in:
Khosrow Moossavi
2020-02-26 14:44:50 -05:00
committed by GitHub
parent 4ff4582dff
commit b716a25811
32 changed files with 2493 additions and 63 deletions

View File

@@ -2,8 +2,10 @@ package types
import (
"bytes"
"encoding/xml"
"go/types"
"reflect"
"sort"
"strings"
)
@@ -21,10 +23,9 @@ type Value interface {
HasDefault() bool
}
// ValueOf returns actual value of a variable
// casted to 'Value' interface. This is done
// to be able to attach specific marshaller func
// to the type (if such a custom function was needed)
// ValueOf returns actual value of a variable casted to 'Default' interface.
// This is done to be able to attach specific marshaller func to the type
// (if such a custom function was needed)
func ValueOf(v interface{}) Value {
if v == nil {
return new(Nil)
@@ -50,9 +51,8 @@ func ValueOf(v interface{}) Value {
return new(Nil)
}
// TypeOf returns Terraform type of a value
// based on provided type by terraform-inspect
// or by looking the underlying type of the value
// TypeOf returns Terraform type of a value based on provided type by
// terraform-inspect or by looking the underlying type of the value
func TypeOf(t string, v interface{}) String {
if t != "" {
return String(t)
@@ -74,9 +74,7 @@ func TypeOf(t string, v interface{}) String {
return String("any")
}
// Nil represents a 'nil' value which is
// marshaled to `null` when empty for JSON
// and YAML
// Nil represents a 'nil' value which is marshaled to `null` when empty for JSON and YAML
type Nil types.Nil
// HasDefault return false for Nil, because there's no value set for the variable
@@ -84,21 +82,24 @@ func (n Nil) HasDefault() bool {
return false
}
// MarshalJSON custom marshal function which
// sets the value to literal `null`
// MarshalJSON custom marshal function which sets the value to literal `null`
func (n Nil) MarshalJSON() ([]byte, error) {
return []byte(`null`), nil
}
// MarshalYAML custom marshal function which
// sets the value to literal `null`
// MarshalXML custom marshal function which adds property 'xsi:nil="true"' to a tag
// of a 'nil' item
func (n Nil) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xsi:nil"}, Value: "true"})
return e.EncodeElement(``, start)
}
// MarshalYAML custom marshal function which sets the value to literal `null`
func (n Nil) MarshalYAML() (interface{}, error) {
return nil, nil
}
// String represents a 'string' value which is
// marshaled to `null` when empty for JSON and
// YAML
// String represents a 'string' value which is marshaled to `null` when empty for JSON and YAML
type String string
// String returns s as an actual string value
@@ -116,8 +117,7 @@ func (s String) HasDefault() bool {
return true
}
// MarshalJSON custom marshal function which
// sets the value to literal `null` when empty
// MarshalJSON custom marshal function which sets the value to literal `null` when empty
func (s String) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
if len(s.String()) == 0 {
@@ -131,8 +131,17 @@ func (s String) MarshalJSON() ([]byte, error) {
return buf.Bytes(), nil
}
// MarshalYAML custom marshal function which
// sets the value to literal `null` when empty
// MarshalXML custom marshal function which adds property 'xsi:nil="true"' to a tag
// if the underlying item is 'nil'
func (s String) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if string(s) == "" {
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xsi:nil"}, Value: "true"})
return e.EncodeElement(``, start)
}
return e.EncodeElement(string(s), start)
}
// MarshalYAML custom marshal function which sets the value to literal `null` when empty
func (s String) MarshalYAML() (interface{}, error) {
if len(s.String()) == 0 {
return nil, nil
@@ -140,8 +149,7 @@ func (s String) MarshalYAML() (interface{}, error) {
return s, nil
}
// Empty represents an empty 'string' which is
// marshaled to `""` in JSON and YAML
// Empty represents an empty 'string' which is marshaled to `""` in JSON and YAML
type Empty string
// nolint
@@ -154,14 +162,12 @@ func (e Empty) HasDefault() bool {
return true
}
// MarshalJSON custom marshal function which
// sets the value to `""`
// MarshalJSON custom marshal function which sets the value to `""`
func (e Empty) MarshalJSON() ([]byte, error) {
return []byte(`""`), nil
}
// Number represents a 'number' value which is
// marshaled to `null` when emty in JSON and YAML
// Number represents a 'number' value which is marshaled to `null` when emty in JSON and YAML
type Number float64
// nolint
@@ -204,6 +210,27 @@ func (l List) HasDefault() bool {
return true
}
type xmllistentry struct {
XMLName xml.Name
Value interface{} `xml:",chardata"`
}
// MarshalXML custom marshal function which wraps list items in '<default></default>'
// tag and each items of the list will be wrapped in a '<item></item>' tag
func (l List) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if len(l) == 0 {
return e.EncodeElement(``, start)
}
err := e.EncodeToken(start)
if err != nil {
return err
}
for _, i := range l {
e.Encode(xmllistentry{XMLName: xml.Name{Local: "item"}, Value: i}) //nolint: errcheck
}
return e.EncodeToken(start.End())
}
// Map represents a 'map' of values
type Map map[string]interface{}
@@ -220,3 +247,64 @@ func (m Map) underlying() map[string]interface{} {
func (m Map) HasDefault() bool {
return true
}
type xmlmapentry struct {
XMLName xml.Name
Value interface{} `xml:",chardata"`
}
type sortmapkeys []string
func (s sortmapkeys) Len() int { return len(s) }
func (s sortmapkeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortmapkeys) Less(i, j int) bool { return s[i] < s[j] }
// MarshalXML custom marshal function which converts map to its literal
// XML representation. For example:
//
// m := Map{
// "a": 1,
// "b": 2,
// "c": 3,
// }
//
// type foo struct {
// Value Map `xml:"value"`
// }
//
// will get marshaled to:
//
// <foo>
// <value>
// <a>1</a>
// <b>2</b>
// <c>3</c>
// </value>
// </foo>
func (m Map) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if len(m) == 0 {
return e.EncodeElement(``, start)
}
err := e.EncodeToken(start)
if err != nil {
return err
}
keys := make([]string, 0)
for k := range m {
keys = append(keys, k)
}
sort.Sort(sortmapkeys(keys))
for _, k := range keys {
switch reflect.TypeOf(m[k]).Kind() {
case reflect.Map:
is := xml.StartElement{Name: xml.Name{Local: k}}
Map(m[k].(map[string]interface{})).MarshalXML(e, is) //nolint: errcheck
case reflect.Slice:
is := xml.StartElement{Name: xml.Name{Local: k}}
List(m[k].([]interface{})).MarshalXML(e, is) //nolint: errcheck
default:
e.Encode(xmlmapentry{XMLName: xml.Name{Local: k}, Value: m[k]}) //nolint: errcheck
}
}
return e.EncodeToken(start.End())
}