mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
resources: Add more details to .Err
This commit adds a .Data object (a map with `Body`, `StatusCode` etc.) to the .Err returned from `resources.GetRemote`, which means you can now do: ``` {{ with .Err }} {{ range $k, $v := .Data }} {{ end }} {{ end }} ``` Fixes #9708
This commit is contained in:
parent
a6fa290f67
commit
9202117ba0
10 changed files with 129 additions and 46 deletions
|
@ -133,7 +133,7 @@ func (p *pageState) reusePageOutputContent() bool {
|
||||||
return p.pageOutputTemplateVariationsState.Load() == 1
|
return p.pageOutputTemplateVariationsState.Load() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) Err() error {
|
func (p *pageState) Err() resource.ResourceError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResourceChainBasic(t *testing.T) {
|
func TestResourceChainBasic(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.FileServer(http.Dir("testdata/")))
|
failIfHandler := func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/fail.jpg" {
|
||||||
|
http.Error(w, "{ msg: failed }", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ts := httptest.NewServer(
|
||||||
|
failIfHandler(http.FileServer(http.Dir("testdata/"))),
|
||||||
|
)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
ts.Close()
|
ts.Close()
|
||||||
})
|
})
|
||||||
|
@ -58,6 +70,7 @@ FIT: {{ $fit.Name }}|{{ $fit.RelPermalink }}|{{ $fit.Width }}
|
||||||
CSS integrity Data first: {{ $cssFingerprinted1.Data.Integrity }} {{ $cssFingerprinted1.RelPermalink }}
|
CSS integrity Data first: {{ $cssFingerprinted1.Data.Integrity }} {{ $cssFingerprinted1.RelPermalink }}
|
||||||
CSS integrity Data last: {{ $cssFingerprinted2.RelPermalink }} {{ $cssFingerprinted2.Data.Integrity }}
|
CSS integrity Data last: {{ $cssFingerprinted2.RelPermalink }} {{ $cssFingerprinted2.Data.Integrity }}
|
||||||
|
|
||||||
|
{{ $failedImg := resources.GetRemote "%[1]s/fail.jpg" }}
|
||||||
{{ $rimg := resources.GetRemote "%[1]s/sunset.jpg" }}
|
{{ $rimg := resources.GetRemote "%[1]s/sunset.jpg" }}
|
||||||
{{ $remotenotfound := resources.GetRemote "%[1]s/notfound.jpg" }}
|
{{ $remotenotfound := resources.GetRemote "%[1]s/notfound.jpg" }}
|
||||||
{{ $localnotfound := resources.Get "images/notfound.jpg" }}
|
{{ $localnotfound := resources.Get "images/notfound.jpg" }}
|
||||||
|
@ -71,7 +84,8 @@ REMOTE NOT FOUND: {{ if $remotenotfound }}FAILED{{ else}}OK{{ end }}
|
||||||
LOCAL NOT FOUND: {{ if $localnotfound }}FAILED{{ else}}OK{{ end }}
|
LOCAL NOT FOUND: {{ if $localnotfound }}FAILED{{ else}}OK{{ end }}
|
||||||
PRINT PROTOCOL ERROR1: {{ with $gopherprotocol }}{{ . | safeHTML }}{{ end }}
|
PRINT PROTOCOL ERROR1: {{ with $gopherprotocol }}{{ . | safeHTML }}{{ end }}
|
||||||
PRINT PROTOCOL ERROR2: {{ with $gopherprotocol }}{{ .Err | safeHTML }}{{ end }}
|
PRINT PROTOCOL ERROR2: {{ with $gopherprotocol }}{{ .Err | safeHTML }}{{ end }}
|
||||||
|
PRINT PROTOCOL ERROR DETAILS: {{ with $gopherprotocol }}Err: {{ .Err | safeHTML }}{{ with .Err }}|{{ with .Data }}Body: {{ .Body }}|StatusCode: {{ .StatusCode }}{{ end }}|{{ end }}{{ end }}
|
||||||
|
FAILED REMOTE ERROR DETAILS CONTENT: {{ with $failedImg.Err }}|{{ . }}|{{ with .Data }}Body: {{ .Body }}|StatusCode: {{ .StatusCode }}|ContentLength: {{ .ContentLength }}|ContentType: {{ .ContentType }}{{ end }}{{ end }}|
|
||||||
`, ts.URL))
|
`, ts.URL))
|
||||||
|
|
||||||
fs := b.Fs.Source
|
fs := b.Fs.Source
|
||||||
|
@ -103,8 +117,9 @@ SUNSET REMOTE: sunset_%[1]s.jpg|/sunset_%[1]s.a9bf1d944e19c0f382e0d8f51de690f7d0
|
||||||
FIT REMOTE: sunset_%[1]s.jpg|/sunset_%[1]s_hu59e56ffff1bc1d8d122b1403d34e039f_0_200x200_fit_q75_box.jpg|200
|
FIT REMOTE: sunset_%[1]s.jpg|/sunset_%[1]s_hu59e56ffff1bc1d8d122b1403d34e039f_0_200x200_fit_q75_box.jpg|200
|
||||||
REMOTE NOT FOUND: OK
|
REMOTE NOT FOUND: OK
|
||||||
LOCAL NOT FOUND: OK
|
LOCAL NOT FOUND: OK
|
||||||
PRINT PROTOCOL ERROR1: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher"
|
PRINT PROTOCOL ERROR DETAILS: Err: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher"||
|
||||||
PRINT PROTOCOL ERROR2: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher"
|
FAILED REMOTE ERROR DETAILS CONTENT: |failed to fetch remote resource: Internal Server Error|Body: { msg: failed }
|
||||||
|
|StatusCode: 500|ContentLength: 16|ContentType: text/plain; charset=utf-8|
|
||||||
|
|
||||||
|
|
||||||
`, helpers.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
|
`, helpers.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
|
||||||
|
|
|
@ -19,9 +19,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/resources/images/exif"
|
"github.com/gohugoio/hugo/resources/images/exif"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,94 +38,94 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewErrorResource wraps err in a Resource where all but the Err method will panic.
|
// NewErrorResource wraps err in a Resource where all but the Err method will panic.
|
||||||
func NewErrorResource(err error) resource.Resource {
|
func NewErrorResource(err resource.ResourceError) resource.Resource {
|
||||||
return &errorResource{error: err}
|
return &errorResource{ResourceError: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorResource struct {
|
type errorResource struct {
|
||||||
error
|
resource.ResourceError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Err() error {
|
func (e *errorResource) Err() resource.ResourceError {
|
||||||
return e.error
|
return e.ResourceError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
|
func (e *errorResource) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Content() (any, error) {
|
func (e *errorResource) Content() (any, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) ResourceType() string {
|
func (e *errorResource) ResourceType() string {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) MediaType() media.Type {
|
func (e *errorResource) MediaType() media.Type {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Permalink() string {
|
func (e *errorResource) Permalink() string {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) RelPermalink() string {
|
func (e *errorResource) RelPermalink() string {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Name() string {
|
func (e *errorResource) Name() string {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Title() string {
|
func (e *errorResource) Title() string {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Params() maps.Params {
|
func (e *errorResource) Params() maps.Params {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Data() any {
|
func (e *errorResource) Data() any {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Height() int {
|
func (e *errorResource) Height() int {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Width() int {
|
func (e *errorResource) Width() int {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Crop(spec string) (resource.Image, error) {
|
func (e *errorResource) Crop(spec string) (resource.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Fill(spec string) (resource.Image, error) {
|
func (e *errorResource) Fill(spec string) (resource.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Fit(spec string) (resource.Image, error) {
|
func (e *errorResource) Fit(spec string) (resource.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Resize(spec string) (resource.Image, error) {
|
func (e *errorResource) Resize(spec string) (resource.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Filter(filters ...any) (resource.Image, error) {
|
func (e *errorResource) Filter(filters ...any) (resource.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Exif() *exif.Exif {
|
func (e *errorResource) Exif() *exif.Exif {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) DecodeImage() (image.Image, error) {
|
func (e *errorResource) DecodeImage() (image.Image, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Transform(...ResourceTransformation) (ResourceTransformer, error) {
|
func (e *errorResource) Transform(...ResourceTransformation) (ResourceTransformer, error) {
|
||||||
panic(e.error)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ var (
|
||||||
// PageNop implements Page, but does nothing.
|
// PageNop implements Page, but does nothing.
|
||||||
type nopPage int
|
type nopPage int
|
||||||
|
|
||||||
func (p *nopPage) Err() error {
|
func (p *nopPage) Err() resource.ResourceError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ type testPage struct {
|
||||||
sectionEntries []string
|
sectionEntries []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *testPage) Err() error {
|
func (p *testPage) Err() resource.ResourceError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ func (l *genericResource) Content() (any, error) {
|
||||||
return l.content, nil
|
return l.content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *genericResource) Err() error {
|
func (r *genericResource) Err() resource.ResourceError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,11 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ ResourceDataProvider = (*resourceError)(nil)
|
||||||
|
_ ResourceError = (*resourceError)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// Cloner is an internal template and not meant for use in the templates. It
|
// Cloner is an internal template and not meant for use in the templates. It
|
||||||
// may change without notice.
|
// may change without notice.
|
||||||
type Cloner interface {
|
type Cloner interface {
|
||||||
|
@ -37,9 +42,33 @@ type OriginProvider interface {
|
||||||
GetFieldString(pattern string) (string, bool)
|
GetFieldString(pattern string) (string, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewResourceError creates a new ResourceError.
|
||||||
|
func NewResourceError(err error, data any) ResourceError {
|
||||||
|
return &resourceError{
|
||||||
|
error: err,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceError struct {
|
||||||
|
error
|
||||||
|
data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data associated with this error.
|
||||||
|
func (e *resourceError) Data() any {
|
||||||
|
return e.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceError is the error return from .Err in Resource in error situations.
|
||||||
|
type ResourceError interface {
|
||||||
|
error
|
||||||
|
ResourceDataProvider
|
||||||
|
}
|
||||||
|
|
||||||
// ErrProvider provides an Err.
|
// ErrProvider provides an Err.
|
||||||
type ErrProvider interface {
|
type ErrProvider interface {
|
||||||
Err() error
|
Err() ResourceError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource represents a linkable resource, i.e. a content page, image etc.
|
// Resource represents a linkable resource, i.e. a content page, image etc.
|
||||||
|
|
|
@ -36,6 +36,41 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HTTPError struct {
|
||||||
|
error
|
||||||
|
Data map[string]any
|
||||||
|
|
||||||
|
StatusCode int
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHTTPError(err error, res *http.Response) *HTTPError {
|
||||||
|
if err == nil {
|
||||||
|
panic("err is nil")
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return &HTTPError{
|
||||||
|
error: err,
|
||||||
|
Data: map[string]any{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body []byte
|
||||||
|
body, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
return &HTTPError{
|
||||||
|
error: err,
|
||||||
|
Data: map[string]any{
|
||||||
|
"StatusCode": res.StatusCode,
|
||||||
|
"Status": res.Status,
|
||||||
|
"Body": string(body),
|
||||||
|
"TransferEncoding": res.TransferEncoding,
|
||||||
|
"ContentLength": res.ContentLength,
|
||||||
|
"ContentType": res.Header.Get("Content-Type"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FromRemote expects one or n-parts of a URL to a resource
|
// FromRemote expects one or n-parts of a URL to a resource
|
||||||
// If you provide multiple parts they will be joined together to the final URL.
|
// If you provide multiple parts they will be joined together to the final URL.
|
||||||
func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resource, error) {
|
func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resource, error) {
|
||||||
|
@ -70,15 +105,16 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != http.StatusNotFound {
|
|
||||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
|
||||||
return nil, errors.Errorf("failed to fetch remote resource: %s", http.StatusText(res.StatusCode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
httpResponse, err := httputil.DumpResponse(res, true)
|
httpResponse, err := httputil.DumpResponse(res, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, toHTTPError(err, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusNotFound {
|
||||||
|
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||||
|
return nil, toHTTPError(errors.Errorf("failed to fetch remote resource: %s", http.StatusText(res.StatusCode)), res)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hugio.ToReadCloser(bytes.NewReader(httpResponse)), nil
|
return hugio.ToReadCloser(bytes.NewReader(httpResponse)), nil
|
||||||
|
|
|
@ -167,7 +167,7 @@ func (r *resourceAdapter) Content() (any, error) {
|
||||||
return r.target.Content()
|
return r.target.Content()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceAdapter) Err() error {
|
func (r *resourceAdapter) Err() resource.ResourceError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,8 +151,13 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource {
|
||||||
|
|
||||||
r, err := get(args...)
|
r, err := get(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This allows the client to reason about the .Err in the template.
|
switch v := err.(type) {
|
||||||
return resources.NewErrorResource(errors.Wrap(err, "error calling resources.GetRemote"))
|
case *create.HTTPError:
|
||||||
|
return resources.NewErrorResource(resource.NewResourceError(v, v.Data))
|
||||||
|
default:
|
||||||
|
return resources.NewErrorResource(resource.NewResourceError(errors.Wrap(err, "error calling resources.GetRemote"), make(map[string]any)))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue