New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding a better error message for download mode #1492
base: main
Are you sure you want to change the base?
Changes from 5 commits
0224e53
fa2631c
834eba4
f45a03b
4715103
e928a0f
32e7c28
a846557
25b5860
36b6d62
2c6b90b
5b85142
65662f5
e962a4d
1b29dcf
d6a7cbd
de71cb7
cc70e12
a5eda16
198d574
ee8f4d1
2cc9f84
9695c36
726b77c
4320101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,15 +18,29 @@ type Mode string | |
|
||
// DownloadMode constants. For more information see config.dev.toml | ||
const ( | ||
Sync Mode = "sync" | ||
Async Mode = "async" | ||
Redirect Mode = "redirect" | ||
AsyncRedirect Mode = "async_redirect" | ||
None Mode = "none" | ||
downloadModeErr = "download mode is not set" | ||
invalidModeErr = "unrecognized download mode: %s" | ||
Sync Mode = "sync" | ||
Async Mode = "async" | ||
Redirect Mode = "redirect" | ||
AsyncRedirect Mode = "async_redirect" | ||
None Mode = "none" | ||
) | ||
|
||
// Validate ensures that m is a valid mode. If this function returns nil, you are | ||
// guaranteed that m is valid | ||
func (m Mode) Validate(op errors.Op) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of it taking an |
||
switch m { | ||
case Sync, Async, Redirect, AsyncRedirect, None: | ||
return nil | ||
default: | ||
return errors.Config( | ||
op, | ||
"mode", | ||
fmt.Sprintf("%s isn't a valid value.", m), | ||
"https://docs.gomods.io/configuration/download/", | ||
) | ||
} | ||
} | ||
|
||
// DownloadFile represents a custom HCL format of | ||
// how to handle module@version requests that are | ||
// not found in storage. | ||
|
@@ -44,17 +58,30 @@ type DownloadPath struct { | |
DownloadURL string `hcl:"downloadURL,optional"` | ||
} | ||
|
||
func (d DownloadPath) validate(op errors.Op) error { | ||
if d.DownloadURL == "" && (d.Mode == Redirect || d.Mode == AsyncRedirect) { | ||
return errors.Config( | ||
op, | ||
fmt.Sprintf("DownloadURL (inside %s in the download file)", d.Pattern), | ||
"You must set a value when the download mode is 'redirect' or 'async_redirect'", | ||
"https://docs.gomods.io/configuration/download/", | ||
) | ||
} | ||
return nil | ||
} | ||
|
||
// NewFile takes a mode and returns a DownloadFile. | ||
// Mode can be one of the constants declared above | ||
// or a custom HCL file. To pass a custom HCL file, | ||
// you can either point to a file path by passing | ||
// file:/path/to/file OR custom:<base64-encoded-hcl> | ||
// directly. | ||
func NewFile(m Mode, downloadURL string) (*DownloadFile, error) { | ||
const downloadModeURL = "https://docs.gomods.io/configuration/download/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make this a package level constant (private) as it is being used 3 more times in the pr |
||
const op errors.Op = "downloadMode.NewFile" | ||
|
||
if m == "" { | ||
return nil, errors.E(op, downloadModeErr) | ||
if err := m.Validate(op); err != nil { | ||
return nil, err | ||
} | ||
|
||
if strings.HasPrefix(string(m), "file:") { | ||
|
@@ -72,12 +99,11 @@ func NewFile(m Mode, downloadURL string) (*DownloadFile, error) { | |
return parseFile(bts) | ||
} | ||
|
||
switch m { | ||
case Sync, Async, Redirect, AsyncRedirect, None: | ||
return &DownloadFile{Mode: m, DownloadURL: downloadURL}, nil | ||
default: | ||
return nil, errors.E(op, errors.KindBadRequest, fmt.Sprintf(invalidModeErr, m)) | ||
retFile := &DownloadFile{Mode: m, DownloadURL: downloadURL} | ||
marwan-at-work marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err := retFile.validate(op); err != nil { | ||
return nil, err | ||
} | ||
return retFile, nil | ||
} | ||
|
||
// parseFile parses an HCL file according to the | ||
|
@@ -93,19 +119,26 @@ func parseFile(file []byte) (*DownloadFile, error) { | |
if dig.HasErrors() { | ||
return nil, errors.E(op, dig.Error()) | ||
} | ||
if err := df.validate(); err != nil { | ||
if err := df.validate(op); err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
return &df, nil | ||
} | ||
|
||
func (d *DownloadFile) validate() error { | ||
const op errors.Op = "downloadMode.validate" | ||
func (d *DownloadFile) validate(op errors.Op) error { | ||
for _, p := range d.Paths { | ||
if err := p.Mode.Validate(op); err != nil { | ||
return err | ||
} | ||
switch p.Mode { | ||
case Sync, Async, Redirect, AsyncRedirect, None: | ||
default: | ||
return errors.E(op, fmt.Errorf("unrecognized mode for %v: %v", p.Pattern, p.Mode)) | ||
return errors.Config( | ||
op, | ||
fmt.Sprintf("mode (in pattern %v", p.Pattern), | ||
fmt.Sprintf("%s is unrecognized", p.Mode), | ||
"https://docs.gomods.io/configuration/download/", | ||
) | ||
} | ||
} | ||
return nil | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
package mode | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/gomods/athens/pkg/errors" | ||
) | ||
|
||
var testCases = []struct { | ||
|
@@ -119,6 +120,7 @@ func TestMode(t *testing.T) { | |
} | ||
|
||
func TestNewFile_err(t *testing.T) { | ||
const op = errors.Op("downloadMode.NewFile") | ||
tc := []struct { | ||
name string | ||
mode Mode | ||
|
@@ -127,12 +129,12 @@ func TestNewFile_err(t *testing.T) { | |
{ | ||
name: "empty mode", | ||
mode: "", | ||
expected: downloadModeErr, | ||
expected: Mode("").Validate(op).Error(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, we probably shouldn't do string matching here: either
|
||
}, | ||
{ | ||
name: "invalid mode", | ||
mode: "invalidMode", | ||
expected: fmt.Sprintf(invalidModeErr, "invalidMode"), | ||
expected: Mode("invalidMode").Validate(op).Error(), | ||
}, | ||
} | ||
for _, c := range tc { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package errors | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// Config returns an error specifically tailored for reporting errors with configuration | ||
// values. You can check for these errors by calling errors.Is(err, KindConfigError) | ||
// (from the github.com/gomods/athens/pkg/errors package). | ||
// | ||
// Generally these kinds of errors should make Athens crash because it was configured | ||
// improperly | ||
func Config(op Op, field, helpText, url string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will this ever be called from outside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope. I put it here just because I think it makes more sense to group a configuration error under the |
||
slc := []string{ | ||
fmt.Sprintf("There was a configuration error with %s. %s", field, helpText), | ||
} | ||
if url != "" { | ||
slc = append(slc, fmt.Sprintf("Please see %s for more information.", url)) | ||
} | ||
return E(op, KindConfigError, strings.Join(slc, " ")) | ||
marwan-at-work marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ const ( | |
KindRateLimit = http.StatusTooManyRequests | ||
KindNotImplemented = http.StatusNotImplemented | ||
KindRedirect = http.StatusMovedPermanently | ||
KindConfigError = -1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the HTTP protocol gives you some room for custom codes It doesn't really matter but just being a bit more conventional :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 422 / There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @marwan-at-work fair enough! I'll take @bluekeyes's suggestion and go with 422, just so we can use a constant. |
||
) | ||
|
||
// Error is an Athens system error. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a
validateConfig
function here: https://github.com/gomods/athens/blob/master/pkg/config/config.go#L238It gets automatically called when you call
config.Load
So this is a bit confusing :)
In the same fashion, the
mode.Validate
function in this case is getting called twice, once here and once inmode.NewFile
.I think we can just:
config.Validate
functionmode.Validate
privateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marwan-at-work I added a
Validate
function on config structs so that we can nest validations. I'll try to move all of the logic intovalidate
struct tags though