Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 49 additions & 14 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
// BodyMode represent the current Body mode
type BodyMode uint

// String to satisfy interface fmt.Stringer
func (m BodyMode) String() string {
switch m {
case BodyInput:
Expand All @@ -32,12 +33,14 @@ const (
BodyFile
)

// Body is our response body content, that will either reference an local file or a runtime-supplied []byte.
type Body struct {
Mode BodyMode
Input []byte
File *os.File
}

// Payload reads out a []byte payload according to it's configuration in Body.BodyMode.
func (body *Body) Payload() []byte {
switch body.Mode {
case BodyInput:
Expand All @@ -47,14 +50,17 @@ func (body *Body) Payload() []byte {
return nil
}

// XXX: Handle this error
bytes, _ := ioutil.ReadAll(body.File)
bytes, err := ioutil.ReadAll(body.File)
if err != nil {
return []byte(fmt.Sprintf("File could not be read: %s \n", body.File.Name()))
}
body.File.Seek(0, 0)
return bytes
}
return nil
return []byte("No body configured.")
}

// Info returns some basic info on the body.
func (body *Body) Info() []byte {
switch body.Mode {
case BodyInput:
Expand All @@ -64,17 +70,20 @@ func (body *Body) Info() []byte {
return nil
}

// XXX: Handle this error
stats, _ := body.File.Stat()
stats, err := body.File.Stat()
if err != nil {
return []byte(fmt.Sprintf("File could not be read: %s \n", body.File.Name()))
}
w := &bytes.Buffer{}
fmt.Fprintf(w, "file: %s\n", body.File.Name())
fmt.Fprintf(w, "size: %d bytes\n", stats.Size())
fmt.Fprintf(w, "perm: %s\n", stats.Mode())
return w.Bytes()
}
return nil
return []byte("No body configured.")
}

// SetFile set a new source file for the body, if it exists.
func (body *Body) SetFile(path string) error {
file, err := os.Open(ExpandPath(path))
if err != nil {
Expand All @@ -86,13 +95,15 @@ func (body *Body) SetFile(path string) error {
return nil
}

// Response is the the preconfigured HTTP response that will be returned to the client.
type Response struct {
Status int
Headers http.Header
Body Body
Delay time.Duration
}

// UnmarshalJSON inflates the Response from []byte representing JSON.
func (r *Response) UnmarshalJSON(data []byte) error {
type alias Response
v := struct {
Expand Down Expand Up @@ -130,6 +141,7 @@ func (r *Response) UnmarshalJSON(data []byte) error {
return nil
}

// MarshalJSON serializes the response into a JSON []byte.
func (r *Response) MarshalJSON() ([]byte, error) {
type alias Response
v := struct {
Expand Down Expand Up @@ -159,6 +171,7 @@ func (r *Response) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}

// NewResponse configures a new response. An empty status will be interpreted as 200 OK.
func NewResponse(status, headers, body string) (*Response, error) {
// Parse Status
status = strings.Trim(status, " \r\n")
Expand All @@ -167,7 +180,7 @@ func NewResponse(status, headers, body string) (*Response, error) {
}
code, err := strconv.Atoi(status)
if err != nil {
return nil, fmt.Errorf("Status: %v", err)
return nil, fmt.Errorf("Could not interpret status: %v", err)
}

if code < 100 || code > 599 {
Expand Down Expand Up @@ -200,6 +213,7 @@ func NewResponse(status, headers, body string) (*Response, error) {
}, nil
}

// Write flushes the body into the ResponseWriter, hence sending it over the wire.
func (r *Response) Write(w http.ResponseWriter) error {
for key := range r.Headers {
w.Header().Set(key, r.Headers.Get(key))
Expand All @@ -210,12 +224,14 @@ func (r *Response) Write(w http.ResponseWriter) error {
return err
}

// ResponsesList holds the multiple configured responses.
type ResponsesList struct {
List map[string]*Response
keys []string
current int
}

// NewResponsesList creates a new empty response list and returns it.
func NewResponsesList() *ResponsesList {
return (&ResponsesList{}).reset()
}
Expand Down Expand Up @@ -247,6 +263,7 @@ func (rl *ResponsesList) load(path string) (map[string]*Response, error) {
return rs.Responses, nil
}

// Load loads a response list from a local JSON document.
func (rl *ResponsesList) Load(path string) error {
rs, err := rl.load(path)
if err != nil {
Expand All @@ -258,14 +275,15 @@ func (rl *ResponsesList) Load(path string) error {
rl.List = rs
}

for key, _ := range rs {
for key := range rs {
rl.keys = append(rl.keys, key)
}
sort.Strings(rl.keys)

return nil
}

// Save saves the current response list to a JSON document on local disk.
func (rl *ResponsesList) Save(path string) error {
f, err := openConfigFile(path)
if err != nil {
Expand Down Expand Up @@ -296,20 +314,36 @@ func (rl *ResponsesList) Save(path string) error {
return nil
}

func (rl *ResponsesList) Next() { rl.current = (rl.current + 1) % len(rl.keys) }
func (rl *ResponsesList) Prev() { rl.current = (rl.current - 1 + len(rl.keys)) % len(rl.keys) }
func (rl *ResponsesList) Cur() *Response { return rl.List[rl.keys[rl.current]] }
func (rl *ResponsesList) Index() int { return rl.current }
func (rl *ResponsesList) Len() int { return len(rl.keys) }
func (rl *ResponsesList) Keys() []string { return rl.keys }
// Next iterates to the next item in the response list.
func (rl *ResponsesList) Next() { rl.current = (rl.current + 1) % len(rl.keys) }

// Prev iterates to the previous item in the response list.
func (rl *ResponsesList) Prev() { rl.current = (rl.current - 1 + len(rl.keys)) % len(rl.keys) }

// Cur retrieves the current response from the response list.
func (rl *ResponsesList) Cur() *Response { return rl.List[rl.keys[rl.current]] }

// Index retrieves the index of the current item in the response list.
func (rl *ResponsesList) Index() int { return rl.current }

// Len reports the length of the response list.
func (rl *ResponsesList) Len() int { return len(rl.keys) }

// Keys retrieves an []string of all keys in the response list.
func (rl *ResponsesList) Keys() []string { return rl.keys }

// Get retrieves a specific response by name from the response list.
func (rl *ResponsesList) Get(key string) *Response { return rl.List[key] }

// Add appends a response item to the list. You need to supply a key for the item.
func (rl *ResponsesList) Add(key string, r *Response) *ResponsesList {
rl.keys = append(rl.keys, key)
sort.Strings(rl.keys)
rl.List[key] = r
return rl
}

// Del removes an item spceified by its key from the response list. It returns false if the item didn't exist at all.
func (rl *ResponsesList) Del(key string) bool {
if _, ok := rl.List[key]; !ok {
return false
Expand All @@ -322,6 +356,7 @@ func (rl *ResponsesList) Del(key string) bool {
return true
}

// ExpandPath expands a given path by replacing '~' with $HOME of the current user.
func ExpandPath(path string) string {
if path[0] == '~' {
path = "$HOME" + path[1:len(path)]
Expand Down
2 changes: 1 addition & 1 deletion response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func TestLoadFromJSON(t *testing.T) {
assert.Equal(t, []byte("<html></html>"), r.Body.Payload())

t.Run("When config file is empty", func(t *testing.T) {
path := time.Now().Format(time.UnixDate)
path := string(time.Now().UnixNano())
defer os.Remove(path)

require.NoError(t, rl.Load(path))
Expand Down