diff --git a/.gitignore b/.gitignore index 4af93cb..58acff5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Binaries -json2xml -json2xml-go +/json2xml +/json2xml-go *.exe *.exe~ *.dll diff --git a/cmd/json2xml-go/main.go b/cmd/json2xml-go/main.go index 77fc9fe..9bb4295 100644 --- a/cmd/json2xml-go/main.go +++ b/cmd/json2xml-go/main.go @@ -49,17 +49,15 @@ import ( json2xml "github.com/vinitkumar/json2xml-go" ) -const version = "1.0.0" +func main() { + os.Exit(run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)) +} -var ( - // Input options +// cliOptions holds all parsed CLI flags. +type cliOptions struct { inputURL string inputString string - - // Output options - outputFile string - - // Conversion options + outputFile string wrapper string root bool pretty bool @@ -68,52 +66,150 @@ var ( xpathFormat bool cdata bool listHeaders bool - - // Other options showVersion bool showHelp bool -) + args []string +} -func init() { - // Input options - flag.StringVar(&inputURL, "u", "", "Read JSON from URL") - flag.StringVar(&inputURL, "url", "", "Read JSON from URL") - flag.StringVar(&inputString, "s", "", "Read JSON from string") - flag.StringVar(&inputString, "string", "", "Read JSON from string") - - // Output options - flag.StringVar(&outputFile, "o", "", "Output file (default: stdout)") - flag.StringVar(&outputFile, "output", "", "Output file (default: stdout)") - - // Conversion options - flag.StringVar(&wrapper, "w", "all", "Wrapper element name") - flag.StringVar(&wrapper, "wrapper", "all", "Wrapper element name") - flag.BoolVar(&root, "r", true, "Include root element") - flag.BoolVar(&root, "root", true, "Include root element") - flag.BoolVar(&pretty, "p", true, "Pretty print output") - flag.BoolVar(&pretty, "pretty", true, "Pretty print output") - flag.BoolVar(&attrType, "t", true, "Include type attributes") - flag.BoolVar(&attrType, "type", true, "Include type attributes") - flag.BoolVar(&itemWrap, "i", true, "Wrap list items in elements") - flag.BoolVar(&itemWrap, "item-wrap", true, "Wrap list items in elements") - flag.BoolVar(&xpathFormat, "x", false, "Use XPath 3.1 json-to-xml format") - flag.BoolVar(&xpathFormat, "xpath", false, "Use XPath 3.1 json-to-xml format") - flag.BoolVar(&cdata, "c", false, "Wrap string values in CDATA sections") - flag.BoolVar(&cdata, "cdata", false, "Wrap string values in CDATA sections") - flag.BoolVar(&listHeaders, "l", false, "Repeat headers for each list item") - flag.BoolVar(&listHeaders, "list-headers", false, "Repeat headers for each list item") - - // Other options - flag.BoolVar(&showVersion, "v", false, "Show version information") - flag.BoolVar(&showVersion, "version", false, "Show version information") - flag.BoolVar(&showHelp, "h", false, "Show help message") - flag.BoolVar(&showHelp, "help", false, "Show help message") - - flag.Usage = usage +func parseFlags(arguments []string) (cliOptions, error) { + var opts cliOptions + fs := flag.NewFlagSet("json2xml-go", flag.ContinueOnError) + + fs.StringVar(&opts.inputURL, "u", "", "Read JSON from URL") + fs.StringVar(&opts.inputURL, "url", "", "Read JSON from URL") + fs.StringVar(&opts.inputString, "s", "", "Read JSON from string") + fs.StringVar(&opts.inputString, "string", "", "Read JSON from string") + fs.StringVar(&opts.outputFile, "o", "", "Output file (default: stdout)") + fs.StringVar(&opts.outputFile, "output", "", "Output file (default: stdout)") + fs.StringVar(&opts.wrapper, "w", "all", "Wrapper element name") + fs.StringVar(&opts.wrapper, "wrapper", "all", "Wrapper element name") + fs.BoolVar(&opts.root, "r", true, "Include root element") + fs.BoolVar(&opts.root, "root", true, "Include root element") + fs.BoolVar(&opts.pretty, "p", true, "Pretty print output") + fs.BoolVar(&opts.pretty, "pretty", true, "Pretty print output") + fs.BoolVar(&opts.attrType, "t", true, "Include type attributes") + fs.BoolVar(&opts.attrType, "type", true, "Include type attributes") + fs.BoolVar(&opts.itemWrap, "i", true, "Wrap list items in elements") + fs.BoolVar(&opts.itemWrap, "item-wrap", true, "Wrap list items in elements") + fs.BoolVar(&opts.xpathFormat, "x", false, "Use XPath 3.1 json-to-xml format") + fs.BoolVar(&opts.xpathFormat, "xpath", false, "Use XPath 3.1 json-to-xml format") + fs.BoolVar(&opts.cdata, "c", false, "Wrap string values in CDATA sections") + fs.BoolVar(&opts.cdata, "cdata", false, "Wrap string values in CDATA sections") + fs.BoolVar(&opts.listHeaders, "l", false, "Repeat headers for each list item") + fs.BoolVar(&opts.listHeaders, "list-headers", false, "Repeat headers for each list item") + fs.BoolVar(&opts.showVersion, "v", false, "Show version information") + fs.BoolVar(&opts.showVersion, "version", false, "Show version information") + fs.BoolVar(&opts.showHelp, "h", false, "Show help message") + fs.BoolVar(&opts.showHelp, "help", false, "Show help message") + + fs.Usage = func() { printUsage(os.Stderr) } + + if err := fs.Parse(arguments); err != nil { + return opts, err + } + opts.args = fs.Args() + return opts, nil } -func usage() { - fmt.Fprintf(os.Stderr, `json2xml-go - Convert JSON to XML +func run(arguments []string, stdin io.Reader, stdout, stderr io.Writer) int { + opts, err := parseFlags(arguments) + if err != nil { + fmt.Fprintf(stderr, "Error: %v\n", err) + return 1 + } + + if opts.showHelp { + printUsage(stderr) + return 0 + } + + if opts.showVersion { + fmt.Fprintf(stdout, "json2xml-go version %s\n", json2xml.Version) + fmt.Fprintf(stdout, "Author: %s <%s>\n", json2xml.Author, json2xml.Email) + return 0 + } + + data, err := readInput(opts, stdin) + if err != nil { + fmt.Fprintf(stderr, "Error reading input: %v\n", err) + return 1 + } + + converter := json2xml.New(data). + WithWrapper(opts.wrapper). + WithRoot(opts.root). + WithPretty(opts.pretty). + WithAttrType(opts.attrType). + WithItemWrap(opts.itemWrap). + WithXPathFormat(opts.xpathFormat) + + xmlOutput, err := converter.ToXMLString() + if err != nil { + fmt.Fprintf(stderr, "Error converting to XML: %v\n", err) + return 1 + } + + if err := writeOutput(xmlOutput, opts.outputFile, stdout); err != nil { + fmt.Fprintf(stderr, "Error writing output: %v\n", err) + return 1 + } + + return 0 +} + +func readInput(opts cliOptions, stdin io.Reader) (any, error) { + if opts.inputURL != "" { + return json2xml.ReadFromURL(opts.inputURL, nil) + } + + if opts.inputString != "" { + return json2xml.ReadFromString(opts.inputString) + } + + if len(opts.args) > 0 { + filename := opts.args[0] + if filename == "-" { + return readFromReader(stdin) + } + return json2xml.ReadFromJSON(filename) + } + + // Check if stdin has data (only when stdin is *os.File) + if f, ok := stdin.(*os.File); ok { + stat, _ := f.Stat() + if (stat.Mode() & os.ModeCharDevice) == 0 { + return readFromReader(stdin) + } + } + + return nil, fmt.Errorf("no input provided. Use -h for help") +} + +func readFromReader(r io.Reader) (any, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("failed to read from stdin: %w", err) + } + + jsonStr := strings.TrimSpace(string(data)) + if jsonStr == "" { + return nil, fmt.Errorf("empty input") + } + + return json2xml.ReadFromString(jsonStr) +} + +func writeOutput(output, outputFile string, stdout io.Writer) error { + if outputFile != "" { + return os.WriteFile(outputFile, []byte(output), 0644) + } + + fmt.Fprintln(stdout, output) + return nil +} + +func printUsage(w io.Writer) { + fmt.Fprintf(w, `json2xml-go - Convert JSON to XML Usage: json2xml-go [flags] [input-file] @@ -167,98 +263,3 @@ Examples: `) } - -func main() { - flag.Parse() - - if showHelp { - usage() - os.Exit(0) - } - - if showVersion { - fmt.Printf("json2xml-go version %s\n", version) - fmt.Printf("Author: %s <%s>\n", json2xml.Author, json2xml.Email) - os.Exit(0) - } - - // Read input data - data, err := readInput() - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) - os.Exit(1) - } - - // Convert to XML - converter := json2xml.New(data). - WithWrapper(wrapper). - WithRoot(root). - WithPretty(pretty). - WithAttrType(attrType). - WithItemWrap(itemWrap). - WithXPathFormat(xpathFormat) - - xmlOutput, err := converter.ToXMLString() - if err != nil { - fmt.Fprintf(os.Stderr, "Error converting to XML: %v\n", err) - os.Exit(1) - } - - // Write output - if err := writeOutput(xmlOutput); err != nil { - fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err) - os.Exit(1) - } -} - -func readInput() (any, error) { - // Priority: URL > String > File > Stdin - if inputURL != "" { - return json2xml.ReadFromURL(inputURL, nil) - } - - if inputString != "" { - return json2xml.ReadFromString(inputString) - } - - args := flag.Args() - if len(args) > 0 { - filename := args[0] - if filename == "-" { - // Read from stdin - return readFromStdin() - } - return json2xml.ReadFromJSON(filename) - } - - // Check if there's data on stdin - stat, _ := os.Stdin.Stat() - if (stat.Mode() & os.ModeCharDevice) == 0 { - return readFromStdin() - } - - return nil, fmt.Errorf("no input provided. Use -h for help") -} - -func readFromStdin() (any, error) { - data, err := io.ReadAll(os.Stdin) - if err != nil { - return nil, fmt.Errorf("failed to read from stdin: %w", err) - } - - jsonStr := strings.TrimSpace(string(data)) - if jsonStr == "" { - return nil, fmt.Errorf("empty input") - } - - return json2xml.ReadFromString(jsonStr) -} - -func writeOutput(output string) error { - if outputFile != "" { - return os.WriteFile(outputFile, []byte(output), 0644) - } - - fmt.Println(output) - return nil -} diff --git a/cmd/json2xml-go/main_test.go b/cmd/json2xml-go/main_test.go new file mode 100644 index 0000000..59ad28c --- /dev/null +++ b/cmd/json2xml-go/main_test.go @@ -0,0 +1,145 @@ +package main + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRunHelp(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-h"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d", code) + } + if !strings.Contains(stderr.String(), "json2xml-go - Convert JSON to XML") { + t.Errorf("expected usage in stderr, got %s", stderr.String()) + } +} + +func TestRunVersion(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-v"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d", code) + } + if !strings.Contains(stdout.String(), "json2xml-go version") { + t.Errorf("expected version in stdout, got %s", stdout.String()) + } +} + +func TestRunFromString(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-s", `{"name":"test"}`, "-p=false", "-t=false"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + if !strings.Contains(stdout.String(), "test") { + t.Errorf("expected XML output, got %s", stdout.String()) + } +} + +func TestRunFromStdin(t *testing.T) { + var stdout, stderr bytes.Buffer + stdin := strings.NewReader(`{"key":"value"}`) + code := run([]string{"-p=false", "-t=false", "-"}, stdin, &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + if !strings.Contains(stdout.String(), "value") { + t.Errorf("expected XML output, got %s", stdout.String()) + } +} + +func TestRunFromFile(t *testing.T) { + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "test.json") + if err := os.WriteFile(tmpFile, []byte(`{"hello":"world"}`), 0644); err != nil { + t.Fatal(err) + } + + var stdout, stderr bytes.Buffer + code := run([]string{"-p=false", "-t=false", tmpFile}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + if !strings.Contains(stdout.String(), "world") { + t.Errorf("expected XML output, got %s", stdout.String()) + } +} + +func TestRunOutputToFile(t *testing.T) { + tmpDir := t.TempDir() + outFile := filepath.Join(tmpDir, "out.xml") + + var stdout, stderr bytes.Buffer + code := run([]string{"-s", `{"a":"b"}`, "-o", outFile, "-p=false", "-t=false"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + + data, err := os.ReadFile(outFile) + if err != nil { + t.Fatalf("failed to read output file: %v", err) + } + if !strings.Contains(string(data), "b") { + t.Errorf("expected XML in output file, got %s", string(data)) + } +} + +func TestRunNoInput(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{}, strings.NewReader(""), &stdout, &stderr) + if code != 1 { + t.Fatalf("expected exit 1, got %d", code) + } + if !strings.Contains(stderr.String(), "no input provided") { + t.Errorf("expected error message, got %s", stderr.String()) + } +} + +func TestRunInvalidJSON(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-s", `{broken`}, strings.NewReader(""), &stdout, &stderr) + if code != 1 { + t.Fatalf("expected exit 1, got %d", code) + } + if !strings.Contains(stderr.String(), "Error reading input") { + t.Errorf("expected error message, got %s", stderr.String()) + } +} + +func TestRunXPathFormat(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-s", `{"n":1}`, "-x", "-p=false"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + if !strings.Contains(stdout.String(), "xpath-functions") { + t.Errorf("expected XPath format, got %s", stdout.String()) + } +} + +func TestRunCustomWrapper(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-s", `{"k":"v"}`, "-w", "myroot", "-p=false", "-t=false"}, strings.NewReader(""), &stdout, &stderr) + if code != 0 { + t.Fatalf("expected exit 0, got %d: %s", code, stderr.String()) + } + if !strings.Contains(stdout.String(), "") { + t.Errorf("expected custom wrapper, got %s", stdout.String()) + } +} + +func TestRunEmptyStdin(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run([]string{"-"}, strings.NewReader(""), &stdout, &stderr) + if code != 1 { + t.Fatalf("expected exit 1, got %d", code) + } + if !strings.Contains(stderr.String(), "empty input") { + t.Errorf("expected empty input error, got %s", stderr.String()) + } +} diff --git a/coverage_test.go b/coverage_test.go index 2e3a983..586cc8e 100644 --- a/coverage_test.go +++ b/coverage_test.go @@ -1097,8 +1097,8 @@ func TestPrettyPrintMoreCases(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if !strings.Contains(result, "`) + // ItemFunc is a function that generates element names for list items. type ItemFunc func(parent string) string @@ -70,7 +74,7 @@ func MakeID(element string, start, end int) string { if end == 0 { end = 999999 } - return fmt.Sprintf("%s_%d", element, rand.Intn(end-start+1)+start) + return fmt.Sprintf("%s_%d", element, rand.IntN(end-start+1)+start) } // GetUniqueID generates a unique ID for a given element. @@ -147,7 +151,7 @@ func KeyIsValidXML(key string) bool { for { tok, err := decoder.Token() if err != nil { - return err.Error() == "EOF" + return err == io.EOF } if tok == nil { break @@ -440,27 +444,45 @@ func Dict2XMLStr(opts Options, attrs map[string]any, item map[string]any, itemNa } // extractSpecialAttrs extracts @attrs, @val, and @flat from an item. +// Returns a shallow copy of item with special keys removed to avoid mutating the caller's data. func extractSpecialAttrs(item map[string]any, defaultAttrs map[string]any) (attrs map[string]any, rawItem any, flat bool) { attrs = defaultAttrs - rawItem = item - if customAttrs, ok := item["@attrs"]; ok { + customAttrs, hasAttrs := item["@attrs"] + val, hasVal := item["@val"] + flatVal, hasFlat := item["@flat"] + + if hasAttrs { if ca, ok := customAttrs.(map[string]any); ok { attrs = ca - delete(item, "@attrs") } } - if val, ok := item["@val"]; ok { + if hasVal { rawItem = val - delete(item, "@val") } - if f, ok := item["@flat"]; ok { - if fb, ok := f.(bool); ok && fb { + if hasFlat { + if fb, ok := flatVal.(bool); ok && fb { flat = true } - delete(item, "@flat") + } + + if !hasAttrs && !hasVal && !hasFlat { + rawItem = item + return attrs, rawItem, flat + } + + cleaned := make(map[string]any, len(item)) + for k, v := range item { + if k == "@attrs" || k == "@val" || k == "@flat" { + continue + } + cleaned[k] = v + } + + if !hasVal { + rawItem = cleaned } return attrs, rawItem, flat @@ -684,7 +706,7 @@ func buildXSINamespace(prefix string, value any) string { } // PrettyPrint formats XML with indentation. -func PrettyPrint(xmlBytes []byte) (string, error) { +func PrettyPrint(xmlBytes []byte) ([]byte, error) { var buf bytes.Buffer decoder := xml.NewDecoder(bytes.NewReader(xmlBytes)) encoder := xml.NewEncoder(&buf) @@ -693,20 +715,20 @@ func PrettyPrint(xmlBytes []byte) (string, error) { for { token, err := decoder.Token() if err != nil { - if err.Error() == "EOF" { + if err == io.EOF { break } - return "", err + return nil, err } if token == nil { break } if err := encoder.EncodeToken(token); err != nil { - return "", err + return nil, err } } if err := encoder.Flush(); err != nil { - return "", err + return nil, err } result := buf.String() @@ -714,10 +736,9 @@ func PrettyPrint(xmlBytes []byte) (string, error) { result = `` + "\n" + result } - re := regexp.MustCompile(`<\?xml[^?]*\?>`) - result = re.ReplaceAllStringFunc(result, func(s string) string { + result = xmlDeclRegexp.ReplaceAllStringFunc(result, func(s string) string { return s + "\n" }) - return result, nil + return []byte(result), nil } diff --git a/dicttoxml_test.go b/dicttoxml_test.go index 86072c7..197b6ff 100644 --- a/dicttoxml_test.go +++ b/dicttoxml_test.go @@ -718,8 +718,8 @@ func TestPrettyPrint(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - if !strings.Contains(result, "\n") { - t.Errorf("expected formatted output with newlines, got %s", result) + if !strings.Contains(string(result), "\n") { + t.Errorf("expected formatted output with newlines, got %s", string(result)) } }) } diff --git a/json2xml.go b/json2xml.go index d720878..9bd9b14 100644 --- a/json2xml.go +++ b/json2xml.go @@ -40,6 +40,8 @@ type JSON2xml struct { attrType bool itemWrap bool xpathFormat bool + cdata bool + listHeaders bool } // New creates a new JSON2xml converter with default options. @@ -52,6 +54,8 @@ func New(data any) *JSON2xml { attrType: true, itemWrap: true, xpathFormat: false, + cdata: false, + listHeaders: false, } } @@ -91,10 +95,21 @@ func (j *JSON2xml) WithXPathFormat(xpathFormat bool) *JSON2xml { return j } -// ToXML converts the data to XML. -// Returns the XML as a string when pretty=true, or as bytes when pretty=false. +// WithCDATA sets whether to wrap string values in CDATA sections. +func (j *JSON2xml) WithCDATA(cdata bool) *JSON2xml { + j.cdata = cdata + return j +} + +// WithListHeaders sets whether to repeat headers for each list item. +func (j *JSON2xml) WithListHeaders(listHeaders bool) *JSON2xml { + j.listHeaders = listHeaders + return j +} + +// ToXML converts the data to XML bytes. // Returns nil if data is empty or nil. -func (j *JSON2xml) ToXML() (any, error) { +func (j *JSON2xml) ToXML() ([]byte, error) { if j.data == nil { return nil, nil } @@ -118,6 +133,8 @@ func (j *JSON2xml) ToXML() (any, error) { ItemWrap: j.itemWrap, ItemFunc: DefaultItemFunc, XPathFormat: j.xpathFormat, + CDATA: j.cdata, + ListHeaders: j.listHeaders, } xmlData := DictToXML(j.data, opts) @@ -139,36 +156,12 @@ func (j *JSON2xml) ToXMLString() (string, error) { if err != nil { return "", err } - if result == nil { - return "", nil - } - - if s, ok := result.(string); ok { - return s, nil - } - if b, ok := result.([]byte); ok { - return string(b), nil - } - return "", nil + return string(result), nil } // ToXMLBytes converts the data to XML and returns it as bytes. func (j *JSON2xml) ToXMLBytes() ([]byte, error) { - result, err := j.ToXML() - if err != nil { - return nil, err - } - if result == nil { - return nil, nil - } - - if s, ok := result.(string); ok { - return []byte(s), nil - } - if b, ok := result.([]byte); ok { - return b, nil - } - return nil, nil + return j.ToXML() } // ConvertToXML is a convenience function to convert JSON data to XML. diff --git a/json2xml_test.go b/json2xml_test.go index 164b747..78f0ba9 100644 --- a/json2xml_test.go +++ b/json2xml_test.go @@ -127,13 +127,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlStr, ok := result.(string) - if !ok { - t.Fatalf("expected string, got %T", result) - } - - if !strings.Contains(xmlStr, `encoding="UTF-8"`) { - t.Errorf("expected encoding declaration, got %s", xmlStr) + if !bytes.Contains(result, []byte(`encoding="UTF-8"`)) { + t.Errorf("expected encoding declaration, got %s", string(result)) } }) @@ -144,13 +139,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes, ok := result.([]byte) - if !ok { - t.Fatalf("expected []byte, got %T", result) - } - - if !bytes.Contains(xmlBytes, []byte(`encoding="UTF-8"`)) { - t.Errorf("expected encoding declaration, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(`encoding="UTF-8"`)) { + t.Errorf("expected encoding declaration, got %s", string(result)) } }) @@ -161,9 +151,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte("")) || !bytes.Contains(xmlBytes, []byte("")) { - t.Errorf("expected custom wrapper, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte("")) || !bytes.Contains(result, []byte("")) { + t.Errorf("expected custom wrapper, got %s", string(result)) } }) @@ -174,9 +163,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if bytes.Contains(xmlBytes, []byte("")) { - t.Errorf("expected no wrapper, got %s", string(xmlBytes)) + if bytes.Contains(result, []byte("")) { + t.Errorf("expected no wrapper, got %s", string(result)) } }) @@ -190,10 +178,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - // Item elements should be present (with or without type attributes) - if !bytes.Contains(xmlBytes, []byte(") if !strings.Contains(xmlStr, "true<") { t.Errorf("expected lowercase 'true', got %s", xmlStr) } @@ -285,18 +266,17 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte(`xmlns="http://www.w3.org/2005/xpath-functions"`)) { - t.Errorf("expected XPath namespace, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(`xmlns="http://www.w3.org/2005/xpath-functions"`)) { + t.Errorf("expected XPath namespace, got %s", string(result)) } - if !bytes.Contains(xmlBytes, []byte(`John`)) { - t.Errorf("expected string element, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(`John`)) { + t.Errorf("expected string element, got %s", string(result)) } - if !bytes.Contains(xmlBytes, []byte(`30`)) { - t.Errorf("expected number element, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(`30`)) { + t.Errorf("expected number element, got %s", string(result)) } - if !bytes.Contains(xmlBytes, []byte(`true`)) { - t.Errorf("expected boolean element, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(`true`)) { + t.Errorf("expected boolean element, got %s", string(result)) } }) @@ -307,9 +287,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte(``)) { - t.Errorf("expected nested map, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(``)) { + t.Errorf("expected nested map, got %s", string(result)) } }) @@ -320,9 +299,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte(``)) { - t.Errorf("expected array element, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(``)) { + t.Errorf("expected array element, got %s", string(result)) } }) @@ -333,9 +311,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte(``)) { - t.Errorf("expected null element, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(``)) { + t.Errorf("expected null element, got %s", string(result)) } }) @@ -346,9 +323,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte("<script>")) { - t.Errorf("expected escaped characters, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte("<script>")) { + t.Errorf("expected escaped characters, got %s", string(result)) } }) @@ -359,9 +335,8 @@ func TestToXML(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - xmlBytes := result.([]byte) - if !bytes.Contains(xmlBytes, []byte(``)) { - t.Errorf("expected array with namespace, got %s", string(xmlBytes)) + if !bytes.Contains(result, []byte(``)) { + t.Errorf("expected array with namespace, got %s", string(result)) } }) } diff --git a/utils.go b/utils.go index c61c63d..fe90707 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "os" + "time" ) // ReadFromJSON reads a JSON file and returns the parsed data. @@ -45,7 +46,7 @@ func ReadFromURL(url string, params map[string]string) (any, error) { req.URL.RawQuery = q.Encode() } - client := &http.Client{} + client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("%w: %v", ErrURLRead, err)