1
0
Fork 0

Adding upstream version 0.8.9.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-22 10:16:14 +02:00
parent 3b2c48b5e4
commit c0c4addb85
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
285 changed files with 25880 additions and 0 deletions

View file

@ -0,0 +1,13 @@
package standard
import (
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
// EnumlessConfig implements the ServiceConfig interface for services that does not use Enum fields.
type EnumlessConfig struct{}
// Enums returns an empty map.
func (ec *EnumlessConfig) Enums() map[string]types.EnumFormatter {
return map[string]types.EnumFormatter{}
}

View file

@ -0,0 +1,7 @@
package standard
// Standard implements the Logger and Templater parts of the Service interface.
type Standard struct {
Logger
Templater
}

View file

@ -0,0 +1,59 @@
package standard
import (
"fmt"
"github.com/nicholas-fedor/shoutrrr/internal/failures"
)
const (
// FailTestSetup is the FailureID used to represent an error that is part of the setup for tests.
FailTestSetup failures.FailureID = -1
// FailParseURL is the FailureID used to represent failing to parse the service URL.
FailParseURL failures.FailureID = -2
// FailServiceInit is the FailureID used to represent failure of a service.Initialize method.
FailServiceInit failures.FailureID = -3
// FailUnknown is the default FailureID.
FailUnknown failures.FailureID = iota
)
type failureLike interface {
failures.Failure
}
// Failure creates a Failure instance corresponding to the provided failureID, wrapping the provided error.
func Failure(failureID failures.FailureID, err error, args ...any) failures.Failure {
messages := map[int]string{
int(FailTestSetup): "test setup failed",
int(FailParseURL): "error parsing Service URL",
int(FailUnknown): "an unknown error occurred",
}
msg := messages[int(failureID)]
if msg == "" {
msg = messages[int(FailUnknown)]
}
// If variadic arguments are provided, format them correctly
if len(args) > 0 {
if format, ok := args[0].(string); ok && len(args) > 1 {
// Treat the first argument as a format string and the rest as its arguments
extraMsg := fmt.Sprintf(format, args[1:]...)
msg = fmt.Sprintf("%s %s", msg, extraMsg)
} else {
// If no format string is provided, just append the arguments as-is
msg = fmt.Sprintf("%s %v", msg, args)
}
}
return failures.Wrap(msg, failureID, err)
}
// IsTestSetupFailure checks whether the given failure is due to the test setup being broken.
func IsTestSetupFailure(failure failureLike) (string, bool) {
if failure != nil && failure.ID() == FailTestSetup {
return "test setup failed: " + failure.Error(), true
}
return "", false
}

View file

@ -0,0 +1,30 @@
package standard
import (
"github.com/nicholas-fedor/shoutrrr/pkg/types"
"github.com/nicholas-fedor/shoutrrr/pkg/util"
)
// Logger provides the utility methods Log* that maps to Logger.Print*.
type Logger struct {
logger types.StdLogger
}
// Logf maps to the service loggers Logger.Printf function.
func (sl *Logger) Logf(format string, v ...any) {
sl.logger.Printf(format, v...)
}
// Log maps to the service loggers Logger.Print function.
func (sl *Logger) Log(v ...any) {
sl.logger.Print(v...)
}
// SetLogger maps the specified logger to the Log* helper methods.
func (sl *Logger) SetLogger(logger types.StdLogger) {
if logger == nil {
sl.logger = util.DiscardLogger
} else {
sl.logger = logger
}
}

View file

@ -0,0 +1,45 @@
package standard
import (
"fmt"
"os"
"text/template"
)
// Templater is the standard implementation of ApplyTemplate using the "text/template" library.
type Templater struct {
templates map[string]*template.Template
}
// GetTemplate attempts to retrieve the template identified with id.
func (templater *Templater) GetTemplate(id string) (*template.Template, bool) {
tpl, found := templater.templates[id]
return tpl, found
}
// SetTemplateString creates a new template from the body and assigns it the id.
func (templater *Templater) SetTemplateString(templateID string, body string) error {
tpl, err := template.New("").Parse(body)
if err != nil {
return fmt.Errorf("parsing template string for ID %q: %w", templateID, err)
}
if templater.templates == nil {
templater.templates = make(map[string]*template.Template, 1)
}
templater.templates[templateID] = tpl
return nil
}
// SetTemplateFile creates a new template from the file and assigns it the id.
func (templater *Templater) SetTemplateFile(templateID string, file string) error {
bytes, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("reading template file %q for ID %q: %w", file, templateID, err)
}
return templater.SetTemplateString(templateID, string(bytes))
}

View file

@ -0,0 +1,205 @@
package standard
import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"testing"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/nicholas-fedor/shoutrrr/internal/failures"
)
func TestStandard(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Shoutrrr Standard Suite")
}
var (
logger *Logger
builder *strings.Builder
stringLogger *log.Logger
)
var _ = ginkgo.Describe("the standard logging implementation", func() {
ginkgo.When("setlogger is called with nil", func() {
ginkgo.It("should provide the logging API without any errors", func() {
logger = &Logger{}
logger.SetLogger(nil)
logger.Log("discarded log message")
gomega.Expect(logger.logger).ToNot(gomega.BeNil())
})
})
ginkgo.When("setlogger is called with a proper logger", func() {
ginkgo.BeforeEach(func() {
logger = &Logger{}
builder = &strings.Builder{}
stringLogger = log.New(builder, "", 0)
})
ginkgo.When("when logger.Log is called", func() {
ginkgo.It("should log messages", func() {
logger.SetLogger(stringLogger)
logger.Log("foo")
logger.Log("bar")
gomega.Expect(builder.String()).To(gomega.Equal("foo\nbar\n"))
})
})
ginkgo.When("when logger.Logf is called", func() {
ginkgo.It("should log messages", func() {
logger.SetLogger(stringLogger)
logger.Logf("foo %d", 7)
gomega.Expect(builder.String()).To(gomega.Equal("foo 7\n"))
})
})
})
})
var _ = ginkgo.Describe("the standard template implementation", func() {
ginkgo.When("a template is being set from a file", func() {
ginkgo.It("should load the template without any errors", func() {
file, err := os.CreateTemp("", "")
if err != nil {
ginkgo.Skip(fmt.Sprintf("Could not create temp file: %s", err))
return
}
fileName := file.Name()
defer os.Remove(fileName)
_, err = io.WriteString(file, "template content")
if err != nil {
ginkgo.Skip(fmt.Sprintf("Could not write to temp file: %s", err))
return
}
templater := &Templater{}
err = templater.SetTemplateFile("foo", fileName)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})
})
ginkgo.When("a template is being set from a file that does not exist", func() {
ginkgo.It("should return an error", func() {
templater := &Templater{}
err := templater.SetTemplateFile("foo", "filename_that_should_not_exist")
gomega.Expect(err).To(gomega.HaveOccurred())
})
})
ginkgo.When("a template is being set with a badly formatted string", func() {
ginkgo.It("should return an error", func() {
templater := &Templater{}
err := templater.SetTemplateString("foo", "template {{ missing end tag")
gomega.Expect(err).To(gomega.HaveOccurred())
})
})
ginkgo.When("a template is being retrieved with a present ID", func() {
ginkgo.It("should return the corresponding template", func() {
templater := &Templater{}
err := templater.SetTemplateString("bar", "template body")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
tpl, found := templater.GetTemplate("bar")
gomega.Expect(tpl).ToNot(gomega.BeNil())
gomega.Expect(found).To(gomega.BeTrue())
})
})
ginkgo.When("a template is being retrieved with an invalid ID", func() {
ginkgo.It("should return an error", func() {
templater := &Templater{}
err := templater.SetTemplateString("bar", "template body")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
tpl, found := templater.GetTemplate("bad ID")
gomega.Expect(tpl).To(gomega.BeNil())
gomega.Expect(found).ToNot(gomega.BeTrue())
})
})
})
var _ = ginkgo.Describe("the standard enumless config implementation", func() {
ginkgo.When("it's enum method is called", func() {
ginkgo.It("should return an empty map", func() {
gomega.Expect((&EnumlessConfig{}).Enums()).To(gomega.BeEmpty())
})
})
})
var _ = ginkgo.Describe("the standard failure implementation", func() {
ginkgo.Describe("Failure function", func() {
ginkgo.When("called with FailParseURL", func() {
ginkgo.It("should return a failure with the correct message", func() {
err := errors.New("invalid URL")
failure := Failure(FailParseURL, err)
gomega.Expect(failure.ID()).To(gomega.Equal(FailParseURL))
gomega.Expect(failure.Error()).
To(gomega.ContainSubstring("error parsing Service URL"))
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("invalid URL"))
})
})
ginkgo.When("called with FailUnknown", func() {
ginkgo.It("should return a failure with the unknown error message", func() {
err := errors.New("something went wrong")
failure := Failure(FailUnknown, err)
gomega.Expect(failure.ID()).To(gomega.Equal(FailUnknown))
gomega.Expect(failure.Error()).
To(gomega.ContainSubstring("an unknown error occurred"))
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("something went wrong"))
})
})
ginkgo.When("called with an unrecognized FailureID", func() {
ginkgo.It("should fallback to the unknown error message", func() {
err := errors.New("unrecognized error")
failure := Failure(failures.FailureID(999), err) // Arbitrary unknown ID
gomega.Expect(failure.ID()).To(gomega.Equal(failures.FailureID(999)))
gomega.Expect(failure.Error()).
To(gomega.ContainSubstring("an unknown error occurred"))
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("unrecognized error"))
})
})
ginkgo.When("called with additional arguments", func() {
ginkgo.It("should include formatted arguments in the error", func() {
err := errors.New("base error")
failure := Failure(FailParseURL, err, "extra info: %s", "details")
gomega.Expect(failure.Error()).
To(gomega.ContainSubstring("error parsing Service URL extra info: details"))
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("base error"))
})
})
})
ginkgo.Describe("IsTestSetupFailure function", func() {
ginkgo.When("called with a FailTestSetup failure", func() {
ginkgo.It("should return true and the correct message", func() {
err := errors.New("setup issue")
failure := Failure(FailTestSetup, err)
msg, isSetupFailure := IsTestSetupFailure(failure)
gomega.Expect(isSetupFailure).To(gomega.BeTrue())
gomega.Expect(msg).To(gomega.ContainSubstring("test setup failed: setup issue"))
})
})
ginkgo.When("called with a different failure", func() {
ginkgo.It("should return false and an empty message", func() {
err := errors.New("parse issue")
failure := Failure(FailParseURL, err)
msg, isSetupFailure := IsTestSetupFailure(failure)
gomega.Expect(isSetupFailure).To(gomega.BeFalse())
gomega.Expect(msg).To(gomega.BeEmpty())
})
})
ginkgo.When("called with nil", func() {
ginkgo.It("should return false and an empty message", func() {
msg, isSetupFailure := IsTestSetupFailure(nil)
gomega.Expect(isSetupFailure).To(gomega.BeFalse())
gomega.Expect(msg).To(gomega.BeEmpty())
})
})
})
})