-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
daemon: JSON responses for API (#546)
* daemon: switch over to chi * update tests, clean up handler attachment * convert all success responses into structured json messages via Render * convert all error responses in auth to proper structured responses * move base response type to package api * update responses, fix tests * reogranize package API * add api.Unmarshal helper * refactor as much of daemon as possible to respect JSON responses * update client (partly) for new responses * update package lock * api package tests * clean up res implementation, update tests * more res api cleanup * improve webhook responses * export res.BaseResponse * fix tests * rename logger to streamer * Fix ErrNotFound code * minor fixes, cleanup * more fixes to env * fix request assignment for streamer, fix omitempty on data * dont assign user on login * Pretty print users
- Loading branch information
Showing
29 changed files
with
903 additions
and
509 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
) | ||
|
||
// BaseResponse is the underlying response structure to all responses. | ||
type BaseResponse struct { | ||
// Basic metadata | ||
HTTPStatusCode int `json:"code"` | ||
RequestID string `json:"request_id,omitempty"` | ||
|
||
// Message is included in all responses, and is a summary of the server's response | ||
Message string `json:"message"` | ||
|
||
// Err contains additional context in the event of an error | ||
Err string `json:"error,omitempty"` | ||
|
||
// Data contains information the server wants to return | ||
Data interface{} `json:"data,omitempty"` | ||
} | ||
|
||
// KV is used for defining specific values to be unmarshalled from BaseResponse | ||
// data | ||
type KV struct { | ||
Key string | ||
Value interface{} | ||
} | ||
|
||
// Unmarshal reads the response and unmarshalls the BaseResponse as well any | ||
// requested key-value pairs. | ||
// For example: | ||
// | ||
// var totpResp = &api.TotpResponse{} | ||
// api.Unmarshal(resp.Body, api.KV{Key: "totp", Value: totpResp}) | ||
// | ||
// Values provided in KV.Value MUST be explicit pointers, even if the value is | ||
// a pointer type, ie maps and slices. | ||
func Unmarshal(r io.Reader, kvs ...KV) (*BaseResponse, error) { | ||
bytes, err := ioutil.ReadAll(r) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not read bytes from reader: %s", err.Error()) | ||
} | ||
|
||
// Unmarshal data into a BaseResponse, replacing BaseResponse.Data with a | ||
// map to preserve raw JSON data in the keys | ||
var ( | ||
data = make(map[string]json.RawMessage) | ||
resp = BaseResponse{Data: &data} | ||
) | ||
if err := json.Unmarshal(bytes, &resp); err != nil { | ||
return nil, fmt.Errorf("could not unmarshal data from reader: %s", err.Error()) | ||
} | ||
|
||
// Unmarshal all requested kv-pairs, silently ignoring errors | ||
for _, kv := range kvs { | ||
json.Unmarshal(data[kv.Key], kv.Value) | ||
} | ||
|
||
return &resp, nil | ||
} | ||
|
||
// Error returns a summary of an encountered error. For more details, you may | ||
// want to interrogate Data. Returns nil if StatusCode is not an HTTP error | ||
// code, ie if the code is in 1xx, 2xx, or 3xx | ||
func (b *BaseResponse) Error() error { | ||
if 100 <= b.HTTPStatusCode && b.HTTPStatusCode < 400 { | ||
return nil | ||
} | ||
if b.Err == "" { | ||
return fmt.Errorf("[error %d] %s", b.HTTPStatusCode, b.Message) | ||
} | ||
return fmt.Errorf("[error %d] %s: (%s)", b.HTTPStatusCode, b.Message, b.Err) | ||
} | ||
|
||
// TotpResponse is used for sending users their Totp secret and backup codes | ||
type TotpResponse struct { | ||
TotpSecret string `json:"secret"` | ||
BackupCodes []string `json:"backup_codes"` | ||
} | ||
|
||
// DeploymentStatus lists details about the deployed project | ||
type DeploymentStatus struct { | ||
InertiaVersion string `json:"version"` | ||
Branch string `json:"branch"` | ||
CommitHash string `json:"commit_hash"` | ||
CommitMessage string `json:"commit_message"` | ||
BuildType string `json:"build_type"` | ||
Containers []string `json:"containers"` | ||
BuildContainerActive bool `json:"build_active"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package api | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
) | ||
|
||
func TestUnmarshal(t *testing.T) { | ||
type args struct { | ||
bytes []byte | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantCode int | ||
wantErr bool | ||
}{ | ||
{"invalid data", | ||
args{nil}, | ||
0, | ||
true}, | ||
{"invalid json", | ||
args{ | ||
[]byte(`{ | ||
"code":200, | ||
"request_id":"bobbook/2Mch7LMzhj-000001", | ||
"mess`), | ||
}, | ||
0, | ||
true}, | ||
{"ok", | ||
args{ | ||
[]byte(`{ | ||
"code":200, | ||
"request_id":"bobbook/2Mch7LMzhj-000001", | ||
"message":"session created", | ||
"data":{ | ||
"token":"blah" | ||
} | ||
}`), | ||
}, | ||
200, | ||
false}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
var gotToken string | ||
var gotKV = []KV{{Key: "token", Value: &gotToken}} | ||
got, err := Unmarshal(bytes.NewReader(tt.args.bytes), gotKV...) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != nil && tt.wantCode != got.HTTPStatusCode { | ||
t.Errorf("Unmarshal() code = %v, want %v", got.HTTPStatusCode, tt.wantCode) | ||
} | ||
for _, kv := range gotKV { | ||
if kv.Value == "" { | ||
t.Error("Unmarshal() kv is empty") | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestBaseResponse_Error(t *testing.T) { | ||
type fields struct { | ||
HTTPStatusCode int | ||
Message string | ||
Err string | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
wantErr bool | ||
}{ | ||
{"not an error", | ||
fields{200, "hi", ""}, | ||
false}, | ||
{"error with only message", | ||
fields{400, "hi", ""}, | ||
true}, | ||
{"error with message and error context", | ||
fields{400, "hi", "oh no"}, | ||
true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
b := &BaseResponse{ | ||
HTTPStatusCode: tt.fields.HTTPStatusCode, | ||
Message: tt.fields.Message, | ||
Err: tt.fields.Err, | ||
} | ||
if err := b.Error(); (err != nil) != tt.wantErr { | ||
t.Errorf("BaseResponse.Error() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.