Skip to content

Commit

Permalink
feat: request to json function usually used as pre-step for storing r…
Browse files Browse the repository at this point in the history
…aw requests
  • Loading branch information
vinayteki95 committed Oct 11, 2024
1 parent fa27ba6 commit 162e9f7
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
57 changes: 57 additions & 0 deletions requesttojson/requesttojson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package requesttojson

import (
"bytes"
"encoding/json"
"io"
"net/http"
)

// Struct to represent the HTTP request in JSON format
type RequestJSON struct {
Method string `json:"method"`
URL string `json:"url"`
Proto string `json:"proto"`
Headers map[string][]string `json:"headers"`
Body string `json:"body"`
Query map[string][]string `json:"query"`
}

func RequestToJSON(req *http.Request) (json.RawMessage, RequestJSON) {
// Parse query parameters from URL
queryParams := req.URL.Query()

// Create a RequestJSON struct to hold the necessary fields
requestJSON := RequestJSON{
Method: req.Method,
URL: req.URL.RequestURI(),
Proto: req.Proto,
Headers: req.Header,
Query: queryParams, // Include query parameters here
}

// Read the body (if present)
if req.Body != nil {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
return json.RawMessage{}, RequestJSON{}
}
requestJSON.Body = string(bodyBytes)
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore body for further reading
}

// Create a buffer to store the JSON output
var buf bytes.Buffer

// Create a JSON encoder and disable HTML escaping
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false) // Disable escaping of special characters like & and +

// Marshal the struct into JSON using the custom encoder
err := encoder.Encode(requestJSON)
if err != nil {
return json.RawMessage{}, RequestJSON{}
}

return json.RawMessage(buf.Bytes()), requestJSON
}
224 changes: 224 additions & 0 deletions requesttojson/requesttojson_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package requesttojson

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestRequestToJSON_SimpleGET(t *testing.T) {
// Create a simple GET request
req, err := http.NewRequest("GET", "http://example.com/path?query=1", nil)
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the struct fields
require.Equal(t, "GET", reqJSON.Method)
require.Equal(t, "/path?query=1", reqJSON.URL)
require.Equal(t, "HTTP/1.1", reqJSON.Proto)
require.Empty(t, reqJSON.Body)
require.Equal(t, map[string][]string{"query": {"1"}}, reqJSON.Query)

// Check the JSON output
require.Contains(t, string(jsonData), `"method":"GET"`)
require.Contains(t, string(jsonData), `"url":"/path?query=1"`)
require.Contains(t, string(jsonData), `"proto":"HTTP/1.1"`)
require.Contains(t, string(jsonData), `"query":{"query":["1"]}`)
}

func TestRequestToJSON_WithHeaders(t *testing.T) {
// Create a POST request with headers
req, err := http.NewRequest("POST", "http://example.com/path", nil)
require.NoError(t, err)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-Custom-Header", "CustomValue")

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the headers in struct
require.Equal(t, map[string][]string{
"Content-Type": {"application/json"},
"X-Custom-Header": {"CustomValue"},
}, reqJSON.Headers)

// Check the JSON output
require.Contains(t, string(jsonData), `"Content-Type":["application/json"]`)
require.Contains(t, string(jsonData), `"X-Custom-Header":["CustomValue"]`)
}

func TestRequestToJSON_WithBody(t *testing.T) {
// Create a POST request with a body
body := `{"key": "value"}`
req, err := http.NewRequest("POST", "http://example.com/path", strings.NewReader(body))
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the body
require.Equal(t, body, reqJSON.Body)

// Check the JSON output
require.Contains(t, string(jsonData), `"body":"{\"key\": \"value\"}"`)
}

func TestRequestToJSON_WithQueryParams(t *testing.T) {
// Create a GET request with query parameters
req, err := http.NewRequest("GET", "http://example.com/search?q=golang&sort=asc", nil)
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the query parameters
expectedQuery := map[string][]string{
"q": {"golang"},
"sort": {"asc"},
}
require.Equal(t, expectedQuery, reqJSON.Query)

// Check the JSON output
require.Contains(t, string(jsonData), `"query":{"q":["golang"],"sort":["asc"]}`)
}

func TestRequestToJSON_EmptyBody(t *testing.T) {
// Create a POST request with an empty body
req, err := http.NewRequest("POST", "http://example.com/path", nil)
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the body is empty
require.Empty(t, reqJSON.Body)

// Check the JSON output
require.Contains(t, string(jsonData), `"body":""`)
}

func TestRequestToJSON_VariousHTTPMethods(t *testing.T) {
methods := []string{"PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}

for _, method := range methods {
// Create a request with different HTTP methods
req, err := http.NewRequest(method, "http://example.com/path", nil)
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the method is correctly set
require.Equal(t, method, reqJSON.Method)
require.Contains(t, string(jsonData), `"method":"`+method+`"`)
}
}

func TestRequestToJSON_URLEncodedBody(t *testing.T) {
// Create a POST request with URL-encoded data
body := "name=John+Doe&age=30"
req, err := http.NewRequest("POST", "http://example.com/form", strings.NewReader(body))
require.NoError(t, err)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check that the body is treated as a raw string
require.Equal(t, body, reqJSON.Body)
require.Contains(t, string(jsonData), `"body":"name=John+Doe&age=30"`)
require.Contains(t, string(jsonData), `"Content-Type":["application/x-www-form-urlencoded"]`)
}

func TestRequestToJSON_MultipleHeadersWithSameKey(t *testing.T) {
// Create a GET request with repeated headers
req, err := http.NewRequest("GET", "http://example.com/path", nil)
require.NoError(t, err)
req.Header.Add("X-Forwarded-For", "192.168.1.1")
req.Header.Add("X-Forwarded-For", "10.0.0.1")

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check the headers in the struct
expectedHeaders := map[string][]string{
"X-Forwarded-For": {"192.168.1.1", "10.0.0.1"},
}
require.Equal(t, expectedHeaders, reqJSON.Headers)

// Check the JSON output
require.Contains(t, string(jsonData), `"X-Forwarded-For":["192.168.1.1","10.0.0.1"]`)
}

func TestRequestToJSON_InvalidJSONBody(t *testing.T) {
// Create a POST request with invalid/malformed JSON
body := `{"name": "John Doe", "age":`
req, err := http.NewRequest("POST", "http://example.com/path", strings.NewReader(body))
require.NoError(t, err)
req.Header.Add("Content-Type", "application/json")

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Check that the raw body is still captured as a string
require.Equal(t, body, reqJSON.Body)

// Check the JSON output
require.Contains(t, string(jsonData), `"body":"{\"name\": \"John Doe\", \"age\":`)
}

func TestRequestToJSON_MultipartFormData(t *testing.T) {
// Create a buffer for the multipart form data
body := &bytes.Buffer{}
writer := io.MultiWriter(body)
_, err := writer.Write([]byte("----WebKitFormBoundary\nContent-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\n\nfilecontent\n----WebKitFormBoundary--"))
require.NoError(t, err)

// Create a POST request with multipart/form-data
req, err := http.NewRequest("POST", "http://example.com/upload", body)
require.NoError(t, err)
req.Header.Add("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary")

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Verify that the body is captured as a raw string
require.Contains(t, reqJSON.Body, "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"")
require.Contains(t, reqJSON.Body, "filecontent")

// Check the JSON output
require.Contains(t, string(jsonData), `"Content-Type":["multipart/form-data; boundary=----WebKitFormBoundary"]`)
}

func TestRequestToJSON_ErrorHandling(t *testing.T) {
// Simulate a request with an unreadable body
body := &readErrorCloser{}
req, err := http.NewRequest("POST", "http://example.com/path", body)
require.NoError(t, err)

// Call the function
jsonData, reqJSON := RequestToJSON(req)

// Ensure the function gracefully returns empty JSON and struct
require.Equal(t, json.RawMessage{}, jsonData)
require.Equal(t, RequestJSON{}, reqJSON)
}

// Helper to simulate a broken request body
type readErrorCloser struct{}

func (*readErrorCloser) Read(p []byte) (n int, err error) {
return 0, fmt.Errorf("read error")
}

func (*readErrorCloser) Close() error {
return nil
}

0 comments on commit 162e9f7

Please sign in to comment.