diff --git a/common/herrors/errors.go b/common/herrors/errors.go index 4d86423629c..598c50b32fd 100644 --- a/common/herrors/errors.go +++ b/common/herrors/errors.go @@ -59,11 +59,34 @@ func GetGID() uint64 { return n } +// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError. +func IsFeatureNotAvailableError(err error) bool { + return errors.Is(err, &FeatureNotAvailableError{}) +} + // ErrFeatureNotAvailable denotes that a feature is unavailable. // // We will, at least to begin with, make some Hugo features (SCSS with libsass) optional, // and this error is used to signal those situations. -var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information") +var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")} + +// FeatureNotAvailableError is an error type used to signal that a feature is not available. +type FeatureNotAvailableError struct { + Cause error +} + +func (e *FeatureNotAvailableError) Unwrap() error { + return e.Cause +} + +func (e *FeatureNotAvailableError) Error() string { + return e.Cause.Error() +} + +func (e *FeatureNotAvailableError) Is(target error) bool { + _, ok := target.(*FeatureNotAvailableError) + return ok +} // Must panics if err != nil. func Must(err error) { diff --git a/common/herrors/errors_test.go b/common/herrors/errors_test.go index 1e07300284e..223782e23ad 100644 --- a/common/herrors/errors_test.go +++ b/common/herrors/errors_test.go @@ -14,6 +14,7 @@ package herrors import ( + "errors" "fmt" "testing" @@ -34,3 +35,12 @@ func TestIsNotExist(t *testing.T) { // os.IsNotExist returns false for wrapped errors. c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true) } + +func TestIsFeatureNotAvailableError(t *testing.T) { + c := qt.New(t) + + c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true) + c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true) + c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false) + +} diff --git a/config/commonConfig.go b/config/commonConfig.go index 2c6497b3488..5cf526708c9 100644 --- a/config/commonConfig.go +++ b/config/commonConfig.go @@ -148,7 +148,7 @@ func (b BuildConfig) UseResourceCache(err error) bool { } if b.UseResourceCacheWhen == "fallback" { - return err == herrors.ErrFeatureNotAvailable + return herrors.IsFeatureNotAvailableError(err) } return true diff --git a/resources/resource_transformers/babel/babel.go b/resources/resource_transformers/babel/babel.go index 5f8fcb00f7e..2999d73cb05 100644 --- a/resources/resource_transformers/babel/babel.go +++ b/resources/resource_transformers/babel/babel.go @@ -181,7 +181,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx if err != nil { if hexec.IsNotFound(err) { // This may be on a CI server etc. Will fall back to pre-built assets. - return herrors.ErrFeatureNotAvailable + return &herrors.FeatureNotAvailableError{Cause: err} } return err } @@ -200,7 +200,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx err = cmd.Run() if err != nil { if hexec.IsNotFound(err) { - return herrors.ErrFeatureNotAvailable + return &herrors.FeatureNotAvailableError{Cause: err} } return fmt.Errorf(errBuf.String()+": %w", err) } diff --git a/resources/resource_transformers/postcss/integration_test.go b/resources/resource_transformers/postcss/integration_test.go index c920fe17d46..74aaa2661c7 100644 --- a/resources/resource_transformers/postcss/integration_test.go +++ b/resources/resource_transformers/postcss/integration_test.go @@ -168,6 +168,25 @@ func TestTransformPostCSSError(t *testing.T) { } +func TestTransformPostCSSNotInstalledError(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + s, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + TxtarString: postCSSIntegrationTestFiles, + }).BuildE() + + s.AssertIsFileError(err) + c.Assert(err.Error(), qt.Contains, `binary with name "npx" not found`) + +} + // #9895 func TestTransformPostCSSImportError(t *testing.T) { if !htesting.IsCI() { diff --git a/resources/resource_transformers/postcss/postcss.go b/resources/resource_transformers/postcss/postcss.go index 0836072468e..a65fa3783a0 100644 --- a/resources/resource_transformers/postcss/postcss.go +++ b/resources/resource_transformers/postcss/postcss.go @@ -205,7 +205,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC if err != nil { if hexec.IsNotFound(err) { // This may be on a CI server etc. Will fall back to pre-built assets. - return herrors.ErrFeatureNotAvailable + return &herrors.FeatureNotAvailableError{Cause: err} } return err } @@ -240,7 +240,9 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC err = cmd.Run() if err != nil { if hexec.IsNotFound(err) { - return herrors.ErrFeatureNotAvailable + return &herrors.FeatureNotAvailableError{ + Cause: err, + } } return imp.toFileError(errBuf.String()) } diff --git a/resources/transform.go b/resources/transform.go index 9e5b576257f..eb7d8fb0b3c 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -449,7 +449,7 @@ func (r *resourceAdapter) transform(publish, setContent bool) error { newErr := func(err error) error { msg := fmt.Sprintf("%s: failed to transform %q (%s)", strings.ToUpper(tr.Key().Name), tctx.InPath, tctx.InMediaType.Type) - if err == herrors.ErrFeatureNotAvailable { + if herrors.IsFeatureNotAvailableError(err) { var errMsg string if tr.Key().Name == "postcss" { // This transformation is not available in this