forked from ebhomengo/niki
352 lines
8.6 KiB
Go
352 lines
8.6 KiB
Go
|
package gofakeit
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"encoding/xml"
|
||
|
"errors"
|
||
|
"math/rand"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
// XMLOptions defines values needed for json generation
|
||
|
type XMLOptions struct {
|
||
|
Type string `json:"type" xml:"type" fake:"{randomstring:[array,single]}"` // single or array
|
||
|
RootElement string `json:"root_element" xml:"root_element"`
|
||
|
RecordElement string `json:"record_element" xml:"record_element"`
|
||
|
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
|
||
|
Indent bool `json:"indent" xml:"indent"`
|
||
|
Fields []Field `json:"fields" xml:"fields" fake:"{fields}"`
|
||
|
}
|
||
|
|
||
|
type xmlArray struct {
|
||
|
XMLName xml.Name
|
||
|
Array []xmlMap
|
||
|
}
|
||
|
|
||
|
type xmlMap struct {
|
||
|
XMLName xml.Name
|
||
|
KeyOrder []string
|
||
|
Map map[string]any `xml:",chardata"`
|
||
|
}
|
||
|
|
||
|
type xmlEntry struct {
|
||
|
XMLName xml.Name
|
||
|
Value any `xml:",chardata"`
|
||
|
}
|
||
|
|
||
|
func (m xmlMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||
|
if len(m.Map) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
start.Name = m.XMLName
|
||
|
|
||
|
err := e.EncodeToken(start)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = xmlMapLoop(e, &m)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return e.EncodeToken(start.End())
|
||
|
}
|
||
|
|
||
|
func xmlMapLoop(e *xml.Encoder, m *xmlMap) error {
|
||
|
var err error
|
||
|
|
||
|
// Check if xmlmap has key order if not create it
|
||
|
// Get key order by order of fields array
|
||
|
if m.KeyOrder == nil {
|
||
|
m.KeyOrder = make([]string, len(m.Map))
|
||
|
for k := range m.Map {
|
||
|
m.KeyOrder = append(m.KeyOrder, k)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, key := range m.KeyOrder {
|
||
|
v := reflect.ValueOf(m.Map[key])
|
||
|
|
||
|
// Always get underlyning Value of value
|
||
|
if v.Kind() == reflect.Ptr {
|
||
|
v = reflect.Indirect(v)
|
||
|
}
|
||
|
|
||
|
switch v.Kind() {
|
||
|
case reflect.Bool,
|
||
|
reflect.String,
|
||
|
reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64,
|
||
|
reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64,
|
||
|
reflect.Float32, reflect.Float64:
|
||
|
err = e.Encode(xmlEntry{XMLName: xml.Name{Local: key}, Value: m.Map[key]})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case reflect.Slice:
|
||
|
e.EncodeToken(xml.StartElement{Name: xml.Name{Local: key}})
|
||
|
for i := 0; i < v.Len(); i++ {
|
||
|
err = e.Encode(xmlEntry{XMLName: xml.Name{Local: "value"}, Value: v.Index(i).String()})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
e.EncodeToken(xml.EndElement{Name: xml.Name{Local: key}})
|
||
|
case reflect.Map:
|
||
|
err = e.Encode(xmlMap{
|
||
|
XMLName: xml.Name{Local: key},
|
||
|
Map: m.Map[key].(map[string]any),
|
||
|
})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case reflect.Struct:
|
||
|
// Convert struct to map[string]any
|
||
|
// So we can rewrap element
|
||
|
var inInterface map[string]any
|
||
|
inrec, _ := json.Marshal(m.Map[key])
|
||
|
json.Unmarshal(inrec, &inInterface)
|
||
|
|
||
|
err = e.Encode(xmlMap{
|
||
|
XMLName: xml.Name{Local: key},
|
||
|
Map: inInterface,
|
||
|
})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
default:
|
||
|
err = e.Encode(m.Map[key])
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// XML generates an object or an array of objects in json format
|
||
|
// A nil XMLOptions returns a randomly structured XML.
|
||
|
func XML(xo *XMLOptions) ([]byte, error) { return xmlFunc(globalFaker, xo) }
|
||
|
|
||
|
// XML generates an object or an array of objects in json format
|
||
|
// A nil XMLOptions returns a randomly structured XML.
|
||
|
func (f *Faker) XML(xo *XMLOptions) ([]byte, error) { return xmlFunc(f, xo) }
|
||
|
|
||
|
func xmlFunc(f *Faker, xo *XMLOptions) ([]byte, error) {
|
||
|
if xo == nil {
|
||
|
// We didn't get a XMLOptions, so create a new random one
|
||
|
err := f.Struct(&xo)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check to make sure they passed in a type
|
||
|
if xo.Type != "single" && xo.Type != "array" {
|
||
|
return nil, errors.New("invalid type, must be array or object")
|
||
|
}
|
||
|
|
||
|
// Check fields length
|
||
|
if xo.Fields == nil || len(xo.Fields) <= 0 {
|
||
|
return nil, errors.New("must pass fields in order to build json object(s)")
|
||
|
}
|
||
|
|
||
|
// Check root element string
|
||
|
if xo.RootElement == "" {
|
||
|
xo.RecordElement = "xml"
|
||
|
}
|
||
|
|
||
|
// Check record element string
|
||
|
if xo.RecordElement == "" {
|
||
|
xo.RecordElement = "record"
|
||
|
}
|
||
|
|
||
|
// Get key order by order of fields array
|
||
|
keyOrder := make([]string, len(xo.Fields))
|
||
|
for _, f := range xo.Fields {
|
||
|
keyOrder = append(keyOrder, f.Name)
|
||
|
}
|
||
|
|
||
|
if xo.Type == "single" {
|
||
|
v := xmlMap{
|
||
|
XMLName: xml.Name{Local: xo.RootElement},
|
||
|
KeyOrder: keyOrder,
|
||
|
Map: make(map[string]any),
|
||
|
}
|
||
|
|
||
|
// Loop through fields and add to them to map[string]any
|
||
|
for _, field := range xo.Fields {
|
||
|
// Get function info
|
||
|
funcInfo := GetFuncLookup(field.Function)
|
||
|
if funcInfo == nil {
|
||
|
return nil, errors.New("invalid function, " + field.Function + " does not exist")
|
||
|
}
|
||
|
|
||
|
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
v.Map[field.Name] = value
|
||
|
}
|
||
|
|
||
|
// Marshal into bytes
|
||
|
var b bytes.Buffer
|
||
|
x := xml.NewEncoder(&b)
|
||
|
if xo.Indent {
|
||
|
x.Indent("", " ")
|
||
|
}
|
||
|
err := x.Encode(v)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return b.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
if xo.Type == "array" {
|
||
|
// Make sure you set a row count
|
||
|
if xo.RowCount <= 0 {
|
||
|
return nil, errors.New("must have row count")
|
||
|
}
|
||
|
|
||
|
xa := xmlArray{
|
||
|
XMLName: xml.Name{Local: xo.RootElement},
|
||
|
Array: make([]xmlMap, xo.RowCount),
|
||
|
}
|
||
|
|
||
|
for i := 1; i <= int(xo.RowCount); i++ {
|
||
|
v := xmlMap{
|
||
|
XMLName: xml.Name{Local: xo.RecordElement},
|
||
|
KeyOrder: keyOrder,
|
||
|
Map: make(map[string]any),
|
||
|
}
|
||
|
|
||
|
// Loop through fields and add to them to map[string]any
|
||
|
for _, field := range xo.Fields {
|
||
|
if field.Function == "autoincrement" {
|
||
|
v.Map[field.Name] = i
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Get function info
|
||
|
funcInfo := GetFuncLookup(field.Function)
|
||
|
if funcInfo == nil {
|
||
|
return nil, errors.New("invalid function, " + field.Function + " does not exist")
|
||
|
}
|
||
|
|
||
|
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
v.Map[field.Name] = value
|
||
|
}
|
||
|
|
||
|
xa.Array = append(xa.Array, v)
|
||
|
}
|
||
|
|
||
|
// Marshal into bytes
|
||
|
var b bytes.Buffer
|
||
|
x := xml.NewEncoder(&b)
|
||
|
if xo.Indent {
|
||
|
x.Indent("", " ")
|
||
|
}
|
||
|
err := x.Encode(xa)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return b.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
return nil, errors.New("invalid type, must be array or object")
|
||
|
}
|
||
|
|
||
|
func addFileXMLLookup() {
|
||
|
AddFuncLookup("xml", Info{
|
||
|
Display: "XML",
|
||
|
Category: "file",
|
||
|
Description: "Generates an single or an array of elements in xml format",
|
||
|
Example: `<xml>
|
||
|
<record>
|
||
|
<first_name>Markus</first_name>
|
||
|
<last_name>Moen</last_name>
|
||
|
<password>Dc0VYXjkWABx</password>
|
||
|
</record>
|
||
|
<record>
|
||
|
<first_name>Osborne</first_name>
|
||
|
<last_name>Hilll</last_name>
|
||
|
<password>XPJ9OVNbs5lm</password>
|
||
|
</record>
|
||
|
</xml>`,
|
||
|
Output: "[]byte",
|
||
|
ContentType: "application/xml",
|
||
|
Params: []Param{
|
||
|
{Field: "type", Display: "Type", Type: "string", Default: "single", Options: []string{"single", "array"}, Description: "Type of XML, single or array"},
|
||
|
{Field: "rootelement", Display: "Root Element", Type: "string", Default: "xml", Description: "Root element wrapper name"},
|
||
|
{Field: "recordelement", Display: "Record Element", Type: "string", Default: "record", Description: "Record element for each record row"},
|
||
|
{Field: "rowcount", Display: "Row Count", Type: "int", Default: "100", Description: "Number of rows in JSON array"},
|
||
|
{Field: "indent", Display: "Indent", Type: "bool", Default: "false", Description: "Whether or not to add indents and newlines"},
|
||
|
{Field: "fields", Display: "Fields", Type: "[]Field", Description: "Fields containing key name and function to run in json format"},
|
||
|
},
|
||
|
Generate: func(r *rand.Rand, m *MapParams, info *Info) (any, error) {
|
||
|
xo := XMLOptions{}
|
||
|
|
||
|
typ, err := info.GetString(m, "type")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xo.Type = typ
|
||
|
|
||
|
rootElement, err := info.GetString(m, "rootelement")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xo.RootElement = rootElement
|
||
|
|
||
|
recordElement, err := info.GetString(m, "recordelement")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xo.RecordElement = recordElement
|
||
|
|
||
|
rowcount, err := info.GetInt(m, "rowcount")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xo.RowCount = rowcount
|
||
|
|
||
|
fieldsStr, err := info.GetStringArray(m, "fields")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
indent, err := info.GetBool(m, "indent")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xo.Indent = indent
|
||
|
|
||
|
// Check to make sure fields has length
|
||
|
if len(fieldsStr) > 0 {
|
||
|
xo.Fields = make([]Field, len(fieldsStr))
|
||
|
|
||
|
for i, f := range fieldsStr {
|
||
|
// Unmarshal fields string into fields array
|
||
|
err = json.Unmarshal([]byte(f), &xo.Fields[i])
|
||
|
if err != nil {
|
||
|
return nil, errors.New("unable to decode json string")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
f := &Faker{Rand: r}
|
||
|
return xmlFunc(f, &xo)
|
||
|
},
|
||
|
})
|
||
|
}
|