gopkg.in/libvirt/libvirt-go-xml.v7@v7.4.0/nwfilter.go (about)

     1  /*
     2   * This file is part of the libvirt-go-xml project
     3   *
     4   * Permission is hereby granted, free of charge, to any person obtaining a copy
     5   * of this software and associated documentation files (the "Software"), to deal
     6   * in the Software without restriction, including without limitation the rights
     7   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   * copies of the Software, and to permit persons to whom the Software is
     9   * furnished to do so, subject to the following conditions:
    10   *
    11   * The above copyright notice and this permission notice shall be included in
    12   * all copies or substantial portions of the Software.
    13   *
    14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20   * THE SOFTWARE.
    21   *
    22   * Copyright (C) 2017 Lian Duan <blazeblue@gmail.com>
    23   *
    24   */
    25  
    26  package libvirtxml
    27  
    28  import (
    29  	"encoding/xml"
    30  	"fmt"
    31  	"io"
    32  	"strconv"
    33  	"strings"
    34  )
    35  
    36  type NWFilter struct {
    37  	XMLName  xml.Name `xml:"filter"`
    38  	Name     string   `xml:"name,attr"`
    39  	UUID     string   `xml:"uuid,omitempty"`
    40  	Chain    string   `xml:"chain,attr,omitempty"`
    41  	Priority int      `xml:"priority,attr,omitempty"`
    42  	Entries  []NWFilterEntry
    43  }
    44  
    45  type NWFilterEntry struct {
    46  	Rule *NWFilterRule
    47  	Ref  *NWFilterRef
    48  }
    49  
    50  type NWFilterRef struct {
    51  	Filter     string              `xml:"filter,attr"`
    52  	Parameters []NWFilterParameter `xml:"parameter"`
    53  }
    54  
    55  type NWFilterParameter struct {
    56  	Name  string `xml:"name,attr"`
    57  	Value string `xml:"value,attr"`
    58  }
    59  
    60  type NWFilterField struct {
    61  	Var  string
    62  	Str  string
    63  	Uint *uint
    64  }
    65  
    66  type NWFilterRule struct {
    67  	Action     string `xml:"action,attr,omitempty"`
    68  	Direction  string `xml:"direction,attr,omitempty"`
    69  	Priority   int    `xml:"priority,attr,omitempty"`
    70  	StateMatch string `xml:"statematch,attr,omitempty"`
    71  
    72  	ARP         *NWFilterRuleARP         `xml:"arp"`
    73  	RARP        *NWFilterRuleRARP        `xml:"rarp"`
    74  	MAC         *NWFilterRuleMAC         `xml:"mac"`
    75  	VLAN        *NWFilterRuleVLAN        `xml:"vlan"`
    76  	STP         *NWFilterRuleSTP         `xml:"stp"`
    77  	IP          *NWFilterRuleIP          `xml:"ip"`
    78  	IPv6        *NWFilterRuleIPv6        `xml:"ipv6"`
    79  	TCP         *NWFilterRuleTCP         `xml:"tcp"`
    80  	UDP         *NWFilterRuleUDP         `xml:"udp"`
    81  	UDPLite     *NWFilterRuleUDPLite     `xml:"udplite"`
    82  	ESP         *NWFilterRuleESP         `xml:"esp"`
    83  	AH          *NWFilterRuleAH          `xml:"ah"`
    84  	SCTP        *NWFilterRuleSCTP        `xml:"sctp"`
    85  	ICMP        *NWFilterRuleICMP        `xml:"icmp"`
    86  	All         *NWFilterRuleAll         `xml:"all"`
    87  	IGMP        *NWFilterRuleIGMP        `xml:"igmp"`
    88  	TCPIPv6     *NWFilterRuleTCPIPv6     `xml:"tcp-ipv6"`
    89  	UDPIPv6     *NWFilterRuleUDPIPv6     `xml:"udp-ipv6"`
    90  	UDPLiteIPv6 *NWFilterRuleUDPLiteIPv6 `xml:"udplite-ipv6"`
    91  	ESPIPv6     *NWFilterRuleESPIPv6     `xml:"esp-ipv6"`
    92  	AHIPv6      *NWFilterRuleAHIPv6      `xml:"ah-ipv6"`
    93  	SCTPIPv6    *NWFilterRuleSCTPIPv6    `xml:"sctp-ipv6"`
    94  	ICMPv6      *NWFilterRuleICMPIPv6    `xml:"icmpv6"`
    95  	AllIPv6     *NWFilterRuleAllIPv6     `xml:"all-ipv6"`
    96  }
    97  
    98  type NWFilterRuleCommonMAC struct {
    99  	SrcMACAddr NWFilterField `xml:"srcmacaddr,attr,omitempty"`
   100  	SrcMACMask NWFilterField `xml:"srcmacmask,attr,omitempty"`
   101  	DstMACAddr NWFilterField `xml:"dstmacaddr,attr,omitempty"`
   102  	DstMACMask NWFilterField `xml:"dstmacmask,attr,omitempty"`
   103  }
   104  
   105  type NWFilterRuleCommonIP struct {
   106  	SrcMACAddr     NWFilterField `xml:"srcmacaddr,attr,omitempty"`
   107  	SrcIPAddr      NWFilterField `xml:"srcipaddr,attr,omitempty"`
   108  	SrcIPMask      NWFilterField `xml:"srcipmask,attr,omitempty"`
   109  	DstIPAddr      NWFilterField `xml:"dstipaddr,attr,omitempty"`
   110  	DstIPMask      NWFilterField `xml:"dstipmask,attr,omitempty"`
   111  	SrcIPFrom      NWFilterField `xml:"srcipfrom,attr,omitempty"`
   112  	SrcIPTo        NWFilterField `xml:"srcipto,attr,omitempty"`
   113  	DstIPFrom      NWFilterField `xml:"dstipfrom,attr,omitempty"`
   114  	DstIPTo        NWFilterField `xml:"dstipto,attr,omitempty"`
   115  	DSCP           NWFilterField `xml:"dscp,attr"`
   116  	ConnLimitAbove NWFilterField `xml:"connlimit-above,attr"`
   117  	State          NWFilterField `xml:"state,attr,omitempty"`
   118  	IPSet          NWFilterField `xml:"ipset,attr,omitempty"`
   119  	IPSetFlags     NWFilterField `xml:"ipsetflags,attr,omitempty"`
   120  }
   121  
   122  type NWFilterRuleCommonPort struct {
   123  	SrcPortStart NWFilterField `xml:"srcportstart,attr"`
   124  	SrcPortEnd   NWFilterField `xml:"srcportend,attr"`
   125  	DstPortStart NWFilterField `xml:"dstportstart,attr"`
   126  	DstPortEnd   NWFilterField `xml:"dstportend,attr"`
   127  }
   128  
   129  type NWFilterRuleARP struct {
   130  	Match string `xml:"match,attr,omitempty"`
   131  	NWFilterRuleCommonMAC
   132  	HWType        NWFilterField `xml:"hwtype,attr"`
   133  	ProtocolType  NWFilterField `xml:"protocoltype,attr"`
   134  	OpCode        NWFilterField `xml:"opcode,attr,omitempty"`
   135  	ARPSrcMACAddr NWFilterField `xml:"arpsrcmacaddr,attr,omitempty"`
   136  	ARPDstMACAddr NWFilterField `xml:"arpdstmacaddr,attr,omitempty"`
   137  	ARPSrcIPAddr  NWFilterField `xml:"arpsrcipaddr,attr,omitempty"`
   138  	ARPSrcIPMask  NWFilterField `xml:"arpsrcipmask,attr,omitempty"`
   139  	ARPDstIPAddr  NWFilterField `xml:"arpdstipaddr,attr,omitempty"`
   140  	ARPDstIPMask  NWFilterField `xml:"arpdstipmask,attr,omitempty"`
   141  	Gratuitous    NWFilterField `xml:"gratuitous,attr,omitempty"`
   142  	Comment       string        `xml:"comment,attr,omitempty"`
   143  }
   144  
   145  type NWFilterRuleRARP struct {
   146  	Match string `xml:"match,attr,omitempty"`
   147  	NWFilterRuleCommonMAC
   148  	HWType        NWFilterField `xml:"hwtype,attr"`
   149  	ProtocolType  NWFilterField `xml:"protocoltype,attr"`
   150  	OpCode        NWFilterField `xml:"opcode,attr,omitempty"`
   151  	ARPSrcMACAddr NWFilterField `xml:"arpsrcmacaddr,attr,omitempty"`
   152  	ARPDstMACAddr NWFilterField `xml:"arpdstmacaddr,attr,omitempty"`
   153  	ARPSrcIPAddr  NWFilterField `xml:"arpsrcipaddr,attr,omitempty"`
   154  	ARPSrcIPMask  NWFilterField `xml:"arpsrcipmask,attr,omitempty"`
   155  	ARPDstIPAddr  NWFilterField `xml:"arpdstipaddr,attr,omitempty"`
   156  	ARPDstIPMask  NWFilterField `xml:"arpdstipmask,attr,omitempty"`
   157  	Gratuitous    NWFilterField `xml:"gratuitous,attr,omitempty"`
   158  	Comment       string        `xml:"comment,attr,omitempty"`
   159  }
   160  
   161  type NWFilterRuleMAC struct {
   162  	Match string `xml:"match,attr,omitempty"`
   163  	NWFilterRuleCommonMAC
   164  	ProtocolID NWFilterField `xml:"protocolid,attr,omitempty"`
   165  	Comment    string        `xml:"comment,attr,omitempty"`
   166  }
   167  
   168  type NWFilterRuleVLAN struct {
   169  	Match string `xml:"match,attr,omitempty"`
   170  	NWFilterRuleCommonMAC
   171  	VLANID        NWFilterField `xml:"vlanid,attr,omitempty"`
   172  	EncapProtocol NWFilterField `xml:"encap-protocol,attr,omitempty"`
   173  	Comment       string        `xml:"comment,attr,omitempty"`
   174  }
   175  
   176  type NWFilterRuleSTP struct {
   177  	Match             NWFilterField `xml:"match,attr,omitempty"`
   178  	SrcMACAddr        NWFilterField `xml:"srcmacaddr,attr,omitempty"`
   179  	SrcMACMask        NWFilterField `xml:"srcmacmask,attr,omitempty"`
   180  	Type              NWFilterField `xml:"type,attr"`
   181  	Flags             NWFilterField `xml:"flags,attr"`
   182  	RootPriority      NWFilterField `xml:"root-priority,attr"`
   183  	RootPriorityHi    NWFilterField `xml:"root-priority-hi,attr"`
   184  	RootAddress       NWFilterField `xml:"root-address,attr,omitempty"`
   185  	RootAddressMask   NWFilterField `xml:"root-address-mask,attr,omitempty"`
   186  	RootCost          NWFilterField `xml:"root-cost,attr"`
   187  	RootCostHi        NWFilterField `xml:"root-cost-hi,attr"`
   188  	SenderPriority    NWFilterField `xml:"sender-priority,attr"`
   189  	SenderPriorityHi  NWFilterField `xml:"sender-priority-hi,attr"`
   190  	SenderAddress     NWFilterField `xml:"sender-address,attr,omitempty"`
   191  	SenderAddressMask NWFilterField `xml:"sender-address-mask,attr,omitempty"`
   192  	Port              NWFilterField `xml:"port,attr"`
   193  	PortHi            NWFilterField `xml:"port-hi,attr"`
   194  	Age               NWFilterField `xml:"age,attr"`
   195  	AgeHi             NWFilterField `xml:"age-hi,attr"`
   196  	MaxAge            NWFilterField `xml:"max-age,attr"`
   197  	MaxAgeHi          NWFilterField `xml:"max-age-hi,attr"`
   198  	HelloTime         NWFilterField `xml:"hello-time,attr"`
   199  	HelloTimeHi       NWFilterField `xml:"hello-time-hi,attr"`
   200  	ForwardDelay      NWFilterField `xml:"forward-delay,attr"`
   201  	ForwardDelayHi    NWFilterField `xml:"forward-delay-hi,attr"`
   202  	Comment           string        `xml:"comment,attr,omitempty"`
   203  }
   204  
   205  type NWFilterRuleIP struct {
   206  	Match string `xml:"match,attr,omitempty"`
   207  	NWFilterRuleCommonMAC
   208  	SrcIPAddr NWFilterField `xml:"srcipaddr,attr,omitempty"`
   209  	SrcIPMask NWFilterField `xml:"srcipmask,attr,omitempty"`
   210  	DstIPAddr NWFilterField `xml:"dstipaddr,attr,omitempty"`
   211  	DstIPMask NWFilterField `xml:"dstipmask,attr,omitempty"`
   212  	Protocol  NWFilterField `xml:"protocol,attr,omitempty"`
   213  	NWFilterRuleCommonPort
   214  	DSCP    NWFilterField `xml:"dscp,attr"`
   215  	Comment string        `xml:"comment,attr,omitempty"`
   216  }
   217  
   218  type NWFilterRuleIPv6 struct {
   219  	Match string `xml:"match,attr,omitempty"`
   220  	NWFilterRuleCommonMAC
   221  	SrcIPAddr NWFilterField `xml:"srcipaddr,attr,omitempty"`
   222  	SrcIPMask NWFilterField `xml:"srcipmask,attr,omitempty"`
   223  	DstIPAddr NWFilterField `xml:"dstipaddr,attr,omitempty"`
   224  	DstIPMask NWFilterField `xml:"dstipmask,attr,omitempty"`
   225  	Protocol  NWFilterField `xml:"protocol,attr,omitempty"`
   226  	NWFilterRuleCommonPort
   227  	Type    NWFilterField `xml:"type,attr"`
   228  	TypeEnd NWFilterField `xml:"typeend,attr"`
   229  	Code    NWFilterField `xml:"code,attr"`
   230  	CodeEnd NWFilterField `xml:"codeend,attr"`
   231  	Comment string        `xml:"comment,attr,omitempty"`
   232  }
   233  
   234  type NWFilterRuleTCP struct {
   235  	Match string `xml:"match,attr,omitempty"`
   236  	NWFilterRuleCommonIP
   237  	NWFilterRuleCommonPort
   238  	Option  NWFilterField `xml:"option,attr"`
   239  	Flags   NWFilterField `xml:"flags,attr,omitempty"`
   240  	Comment string        `xml:"comment,attr,omitempty"`
   241  }
   242  
   243  type NWFilterRuleUDP struct {
   244  	Match string `xml:"match,attr,omitempty"`
   245  	NWFilterRuleCommonIP
   246  	NWFilterRuleCommonPort
   247  	Comment string `xml:"comment,attr,omitempty"`
   248  }
   249  
   250  type NWFilterRuleUDPLite struct {
   251  	Match string `xml:"match,attr,omitempty"`
   252  	NWFilterRuleCommonIP
   253  	Comment string `xml:"comment,attr,omitempty"`
   254  }
   255  
   256  type NWFilterRuleESP struct {
   257  	Match string `xml:"match,attr,omitempty"`
   258  	NWFilterRuleCommonIP
   259  	Comment string `xml:"comment,attr,omitempty"`
   260  }
   261  
   262  type NWFilterRuleAH struct {
   263  	Match string `xml:"match,attr,omitempty"`
   264  	NWFilterRuleCommonIP
   265  	Comment string `xml:"comment,attr,omitempty"`
   266  }
   267  
   268  type NWFilterRuleSCTP struct {
   269  	Match string `xml:"match,attr,omitempty"`
   270  	NWFilterRuleCommonIP
   271  	NWFilterRuleCommonPort
   272  	Comment string `xml:"comment,attr,omitempty"`
   273  }
   274  
   275  type NWFilterRuleICMP struct {
   276  	Match string `xml:"match,attr,omitempty"`
   277  	NWFilterRuleCommonIP
   278  	Type    NWFilterField `xml:"type,attr"`
   279  	Code    NWFilterField `xml:"code,attr"`
   280  	Comment string        `xml:"comment,attr,omitempty"`
   281  }
   282  
   283  type NWFilterRuleAll struct {
   284  	Match string `xml:"match,attr,omitempty"`
   285  	NWFilterRuleCommonIP
   286  	Comment string `xml:"comment,attr,omitempty"`
   287  }
   288  
   289  type NWFilterRuleIGMP struct {
   290  	Match string `xml:"match,attr,omitempty"`
   291  	NWFilterRuleCommonIP
   292  	Comment string `xml:"comment,attr,omitempty"`
   293  }
   294  
   295  type NWFilterRuleTCPIPv6 struct {
   296  	Match string `xml:"match,attr,omitempty"`
   297  	NWFilterRuleCommonIP
   298  	NWFilterRuleCommonPort
   299  	Option  NWFilterField `xml:"option,attr"`
   300  	Comment string        `xml:"comment,attr,omitempty"`
   301  }
   302  
   303  type NWFilterRuleUDPIPv6 struct {
   304  	Match string `xml:"match,attr,omitempty"`
   305  	NWFilterRuleCommonIP
   306  	NWFilterRuleCommonPort
   307  	Comment string `xml:"comment,attr,omitempty"`
   308  }
   309  
   310  type NWFilterRuleUDPLiteIPv6 struct {
   311  	Match string `xml:"match,attr,omitempty"`
   312  	NWFilterRuleCommonIP
   313  	Comment string `xml:"comment,attr,omitempty"`
   314  }
   315  
   316  type NWFilterRuleESPIPv6 struct {
   317  	Match string `xml:"match,attr,omitempty"`
   318  	NWFilterRuleCommonIP
   319  	Comment string `xml:"comment,attr,omitempty"`
   320  }
   321  
   322  type NWFilterRuleAHIPv6 struct {
   323  	Match string `xml:"match,attr,omitempty"`
   324  	NWFilterRuleCommonIP
   325  	Comment string `xml:"comment,attr,omitempty"`
   326  }
   327  
   328  type NWFilterRuleSCTPIPv6 struct {
   329  	Match string `xml:"match,attr,omitempty"`
   330  	NWFilterRuleCommonIP
   331  	NWFilterRuleCommonPort
   332  	Comment string `xml:"comment,attr,omitempty"`
   333  }
   334  
   335  type NWFilterRuleICMPIPv6 struct {
   336  	Match string `xml:"match,attr,omitempty"`
   337  	NWFilterRuleCommonIP
   338  	Type    NWFilterField `xml:"type,attr"`
   339  	Code    NWFilterField `xml:"code,attr"`
   340  	Comment string        `xml:"comment,attr,omitempty"`
   341  }
   342  
   343  type NWFilterRuleAllIPv6 struct {
   344  	Match string `xml:"match,attr,omitempty"`
   345  	NWFilterRuleCommonIP
   346  	Comment string `xml:"comment,attr,omitempty"`
   347  }
   348  
   349  func (s *NWFilterField) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
   350  	if s == nil {
   351  		return xml.Attr{}, nil
   352  	}
   353  	if s.Str != "" {
   354  		return xml.Attr{
   355  			Name:  name,
   356  			Value: s.Str,
   357  		}, nil
   358  	} else if s.Var != "" {
   359  		return xml.Attr{
   360  			Name:  name,
   361  			Value: "$" + s.Str,
   362  		}, nil
   363  	} else if s.Uint != nil {
   364  		return xml.Attr{
   365  			Name:  name,
   366  			Value: fmt.Sprintf("0x%x", *s.Uint),
   367  		}, nil
   368  	} else {
   369  		return xml.Attr{}, nil
   370  	}
   371  }
   372  
   373  func (s *NWFilterField) UnmarshalXMLAttr(attr xml.Attr) error {
   374  	if attr.Value == "" {
   375  		return nil
   376  	}
   377  	if attr.Value[0] == '$' {
   378  		s.Var = attr.Value[1:]
   379  	}
   380  	if strings.HasPrefix(attr.Value, "0x") {
   381  		val, err := strconv.ParseUint(attr.Value[2:], 16, 64)
   382  		if err != nil {
   383  			return err
   384  		}
   385  		uval := uint(val)
   386  		s.Uint = &uval
   387  	}
   388  	s.Str = attr.Value
   389  	return nil
   390  }
   391  
   392  func (a *NWFilter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
   393  	start.Name.Local = "filter"
   394  	start.Attr = append(start.Attr, xml.Attr{
   395  		Name:  xml.Name{Local: "name"},
   396  		Value: a.Name,
   397  	})
   398  	if a.Chain != "" {
   399  		start.Attr = append(start.Attr, xml.Attr{
   400  			Name:  xml.Name{Local: "chain"},
   401  			Value: a.Chain,
   402  		})
   403  	}
   404  	if a.Priority != 0 {
   405  		start.Attr = append(start.Attr, xml.Attr{
   406  			Name:  xml.Name{Local: "priority"},
   407  			Value: fmt.Sprintf("%d", a.Priority),
   408  		})
   409  	}
   410  	err := e.EncodeToken(start)
   411  	if err != nil {
   412  		return err
   413  	}
   414  	if a.UUID != "" {
   415  		uuid := xml.StartElement{
   416  			Name: xml.Name{Local: "uuid"},
   417  		}
   418  		e.EncodeToken(uuid)
   419  		e.EncodeToken(xml.CharData(a.UUID))
   420  		e.EncodeToken(uuid.End())
   421  	}
   422  
   423  	for _, entry := range a.Entries {
   424  		if entry.Rule != nil {
   425  			rule := xml.StartElement{
   426  				Name: xml.Name{Local: "rule"},
   427  			}
   428  			e.EncodeElement(entry.Rule, rule)
   429  		} else if entry.Ref != nil {
   430  			ref := xml.StartElement{
   431  				Name: xml.Name{Local: "filterref"},
   432  			}
   433  			e.EncodeElement(entry.Ref, ref)
   434  		}
   435  	}
   436  
   437  	err = e.EncodeToken(start.End())
   438  	if err != nil {
   439  		return err
   440  	}
   441  	return nil
   442  }
   443  
   444  func (a *NWFilter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
   445  	name, ok := getAttr(start.Attr, "name")
   446  	if !ok {
   447  		return fmt.Errorf("Missing filter name")
   448  	}
   449  	a.Name = name
   450  	a.Chain, _ = getAttr(start.Attr, "chain")
   451  	prio, ok := getAttr(start.Attr, "priority")
   452  	if ok {
   453  		val, err := strconv.ParseInt(prio, 10, 64)
   454  		if err != nil {
   455  			return err
   456  		}
   457  		a.Priority = int(val)
   458  	}
   459  
   460  	for {
   461  		tok, err := d.Token()
   462  		if err == io.EOF {
   463  			break
   464  		}
   465  
   466  		switch tok := tok.(type) {
   467  		case xml.StartElement:
   468  			{
   469  				if tok.Name.Local == "uuid" {
   470  					txt, err := d.Token()
   471  					if err != nil {
   472  						return err
   473  					}
   474  
   475  					txt2, ok := txt.(xml.CharData)
   476  					if !ok {
   477  						return fmt.Errorf("Expected UUID string")
   478  					}
   479  					a.UUID = string(txt2)
   480  				} else if tok.Name.Local == "rule" {
   481  					entry := NWFilterEntry{
   482  						Rule: &NWFilterRule{},
   483  					}
   484  
   485  					d.DecodeElement(entry.Rule, &tok)
   486  
   487  					a.Entries = append(a.Entries, entry)
   488  				} else if tok.Name.Local == "filterref" {
   489  					entry := NWFilterEntry{
   490  						Ref: &NWFilterRef{},
   491  					}
   492  
   493  					d.DecodeElement(entry.Ref, &tok)
   494  
   495  					a.Entries = append(a.Entries, entry)
   496  				}
   497  			}
   498  		}
   499  
   500  	}
   501  	return nil
   502  }
   503  
   504  func (s *NWFilter) Unmarshal(doc string) error {
   505  	return xml.Unmarshal([]byte(doc), s)
   506  }
   507  
   508  func (s *NWFilter) Marshal() (string, error) {
   509  	doc, err := xml.MarshalIndent(s, "", "  ")
   510  	if err != nil {
   511  		return "", err
   512  	}
   513  	return string(doc), nil
   514  }