core: add type registry

Facilitates validation of type adherence to namespace requirements
This commit is contained in:
Mohammed Al Sahaf 2022-06-12 22:35:11 +03:00
parent b16aba5c27
commit 5e24e84288
17 changed files with 180 additions and 0 deletions

View file

@ -34,6 +34,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -55,6 +56,9 @@ func init() {
if env, exists := os.LookupEnv("CADDY_ADMIN"); exists { if env, exists := os.LookupEnv("CADDY_ADMIN"); exists {
DefaultAdminListen = env DefaultAdminListen = env
} }
RegisterType("caddy.config_loaders", []reflect.Type{
reflect.TypeOf((*ConfigLoader)(nil)).Elem(),
})
} }
// AdminConfig configures Caddy's API endpoint, which is used // AdminConfig configures Caddy's API endpoint, which is used

View file

@ -27,6 +27,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
"strings" "strings"
@ -41,6 +42,15 @@ import (
"github.com/caddyserver/caddy/v2/notify" "github.com/caddyserver/caddy/v2/notify"
) )
func init() {
RegisterType("", []reflect.Type{
reflect.TypeOf((*App)(nil)).Elem(),
})
RegisterType("caddy.storage", []reflect.Type{
reflect.TypeOf((*StorageConverter)(nil)).Elem(),
})
}
// Config is the top (or beginning) of the Caddy configuration structure. // Config is the top (or beginning) of the Caddy configuration structure.
// Caddy config is expressed natively as a JSON document. If you prefer // Caddy config is expressed natively as a JSON document. If you prefer
// not to work with JSON directly, there are [many config adapters](/docs/config-adapters) // not to work with JSON directly, there are [many config adapters](/docs/config-adapters)
@ -72,11 +82,15 @@ type Config struct {
// module is `caddy.storage.file_system` (the local file system), // module is `caddy.storage.file_system` (the local file system),
// and the default path // and the default path
// [depends on the OS and environment](/docs/conventions#data-directory). // [depends on the OS and environment](/docs/conventions#data-directory).
// A storage `module` should implement the following interfaces:
// - [StorageConverter](https://pkg.go.dev/github.com/caddyserver/caddy/v2#StorageConverter)
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
// AppsRaw are the apps that Caddy will load and run. The // AppsRaw are the apps that Caddy will load and run. The
// app module name is the key, and the app's config is the // app module name is the key, and the app's config is the
// associated value. // associated value.
// An `app` should implement the following interfaces:
// - [caddy.App](https://pkg.go.dev/github.com/caddyserver/caddy/v2?tab=doc#App)
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="` AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
apps map[string]App apps map[string]App

View file

@ -0,0 +1,22 @@
package integration
import (
"testing"
"github.com/caddyserver/caddy/v2"
_ "github.com/caddyserver/caddy/v2/modules/standard"
)
// Validates Caddy's the registered internal types implement the necessary interfaces of their
// namespaces
func TestTypes(t *testing.T) {
var i int
for _, v := range caddy.Modules() {
mod, _ := caddy.GetModule(v)
if ok, err := caddy.ConformsToNamespace(mod.New(), mod.ID.Namespace()); !ok {
t.Errorf("%s", err)
}
i++
}
t.Logf("Passed through %d modules", i)
}

View file

@ -24,6 +24,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -37,6 +38,12 @@ import (
"github.com/caddyserver/caddy/v2/internal" "github.com/caddyserver/caddy/v2/internal"
) )
func init() {
RegisterType("caddy.listeners", []reflect.Type{
reflect.TypeOf((*ListenerWrapper)(nil)).Elem(),
})
}
// NetworkAddress represents one or more network addresses. // NetworkAddress represents one or more network addresses.
// It contains the individual components for a parsed network // It contains the individual components for a parsed network
// address of the form accepted by ParseNetworkAddress(). // address of the form accepted by ParseNetworkAddress().

View file

@ -20,6 +20,7 @@ import (
"io" "io"
"log" "log"
"os" "os"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -33,6 +34,12 @@ func init() {
RegisterModule(StdoutWriter{}) RegisterModule(StdoutWriter{})
RegisterModule(StderrWriter{}) RegisterModule(StderrWriter{})
RegisterModule(DiscardWriter{}) RegisterModule(DiscardWriter{})
RegisterType("caddy.logging.encoders", []reflect.Type{
reflect.TypeOf((*zapcore.Encoder)(nil)).Elem(),
})
RegisterType("caddy.logging.writers", []reflect.Type{
reflect.TypeOf((*WriterOpener)(nil)).Elem(),
})
} }
// Logging facilitates logging within Caddy. The default log is // Logging facilitates logging within Caddy. The default log is
@ -265,6 +272,8 @@ type BaseLog struct {
WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"` WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
// The encoder is how the log entries are formatted or encoded. // The encoder is how the log entries are formatted or encoded.
// An `encoder` should implement the following interfaces:
// - [zapcore.Encoder](https://pkg.go.dev/go.uber.org/zap/zapcore#Encoder)
EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
// Level is the minimum level to emit, and is inclusive. // Level is the minimum level to emit, and is inclusive.

View file

@ -21,8 +21,10 @@ import (
"fmt" "fmt"
weakrand "math/rand" weakrand "math/rand"
"net/http" "net/http"
"reflect"
"strings" "strings"
"sync" "sync"
"time"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
@ -31,6 +33,15 @@ import (
func init() { func init() {
caddy.RegisterModule(HTTPBasicAuth{}) caddy.RegisterModule(HTTPBasicAuth{})
caddy.RegisterType("http.authentication.hashes", []reflect.Type{
reflect.TypeOf((*Comparer)(nil)).Elem(),
})
caddy.RegisterType("http.authentication.providers", []reflect.Type{
reflect.TypeOf((*Authenticator)(nil)).Elem(),
})
weakrand.Seed(time.Now().UnixNano())
} }
// HTTPBasicAuth facilitates HTTP basic authentication. // HTTPBasicAuth facilitates HTTP basic authentication.

View file

@ -26,6 +26,7 @@ import (
"math" "math"
"net" "net"
"net/http" "net/http"
"reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -37,6 +38,9 @@ import (
func init() { func init() {
caddy.RegisterModule(Encode{}) caddy.RegisterModule(Encode{})
caddy.RegisterType("http.encoders", []reflect.Type{
reflect.TypeOf((*Encoding)(nil)).Elem(),
})
} }
// Encode is a middleware which can encode responses. // Encode is a middleware which can encode responses.

View file

@ -26,9 +26,11 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"go.uber.org/zap" "go.uber.org/zap"
@ -38,6 +40,12 @@ import (
) )
func init() { func init() {
weakrand.Seed(time.Now().UnixNano())
caddy.RegisterType("http.precompressed", []reflect.Type{
reflect.TypeOf((*encode.Precompressed)(nil)).Elem(),
})
caddy.RegisterModule(FileServer{}) caddy.RegisterModule(FileServer{})
} }

View file

@ -197,6 +197,8 @@ type (
// where each of the array elements is a matcher set, i.e. an // where each of the array elements is a matcher set, i.e. an
// object keyed by matcher name. // object keyed by matcher name.
MatchNot struct { MatchNot struct {
// A `matcher` should implement the following interfaces:
// - [caddyhttp.RequestMatcher](https://pkg.go.dev/github.com/caddyserver/caddy/v2/modules/caddyhttp?tab=doc#RequestMatcher)
MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"` MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
MatcherSets []MatcherSet `json:"-"` MatcherSets []MatcherSet `json:"-"`
} }
@ -212,6 +214,9 @@ func init() {
caddy.RegisterModule(MatchHeaderRE{}) caddy.RegisterModule(MatchHeaderRE{})
caddy.RegisterModule(new(MatchProtocol)) caddy.RegisterModule(new(MatchProtocol))
caddy.RegisterModule(MatchNot{}) caddy.RegisterModule(MatchNot{})
caddy.RegisterType("http.matchers", []reflect.Type{
reflect.TypeOf((*RequestMatcher)(nil)).Elem(),
})
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.

View file

@ -1145,3 +1145,9 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
match.Match(req) match.Match(req)
} }
} }
func TestConformsToNamespace(t *testing.T) {
if ok, err := caddy.ConformsToNamespace(new(StaticResponse), "http.matchers"); !ok || err != nil {
t.Errorf("%s", err)
}
}

View file

@ -27,6 +27,7 @@ import (
"net/netip" "net/netip"
"net/textproto" "net/textproto"
"net/url" "net/url"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -45,6 +46,19 @@ import (
func init() { func init() {
caddy.RegisterModule(Handler{}) caddy.RegisterModule(Handler{})
caddy.RegisterType("http.reverse_proxy.circuit_breakers", []reflect.Type{
reflect.TypeOf((*CircuitBreaker)(nil)).Elem(),
})
caddy.RegisterType("http.reverse_proxy.selection_policies", []reflect.Type{
reflect.TypeOf((*Selector)(nil)).Elem(),
})
caddy.RegisterType("http.reverse_proxy.transport", []reflect.Type{
reflect.TypeOf((*http.RoundTripper)(nil)).Elem(),
})
caddy.RegisterType("http.reverse_proxy.upstreams", []reflect.Type{
reflect.TypeOf((*UpstreamSource)(nil)).Elem(),
})
} }
// Handler implements a highly configurable and production-ready reverse proxy. // Handler implements a highly configurable and production-ready reverse proxy.

View file

@ -18,10 +18,17 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
func init() {
caddy.RegisterType("http.handlers", []reflect.Type{
reflect.TypeOf((*MiddlewareHandler)(nil)).Elem(),
})
}
// Route consists of a set of rules for matching HTTP requests, // Route consists of a set of rules for matching HTTP requests,
// a list of handlers to execute, and optional flow control // a list of handlers to execute, and optional flow control
// parameters which customize the handling of HTTP requests // parameters which customize the handling of HTTP requests

View file

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"strings" "strings"
"time" "time"
@ -30,6 +31,18 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
func init() {
caddy.RegisterType("dns.provider", []reflect.Type{
reflect.TypeOf((*acmez.Solver)(nil)).Elem(),
})
caddy.RegisterType("tls.get_certificate", []reflect.Type{
reflect.TypeOf((*certmagic.Manager)(nil)).Elem(),
})
caddy.RegisterType("tls.issuance", []reflect.Type{
reflect.TypeOf((*certmagic.Issuer)(nil)).Elem(),
})
}
// AutomationConfig governs the automated management of TLS certificates. // AutomationConfig governs the automated management of TLS certificates.
type AutomationConfig struct { type AutomationConfig struct {
// The list of automation policies. The first policy matching // The list of automation policies. The first policy matching

View file

@ -23,6 +23,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"github.com/mholt/acmez" "github.com/mholt/acmez"
@ -33,6 +34,9 @@ import (
func init() { func init() {
caddy.RegisterModule(LeafCertClientAuth{}) caddy.RegisterModule(LeafCertClientAuth{})
caddy.RegisterType("tls.handshake_match", []reflect.Type{
reflect.TypeOf((*ConnectionMatcher)(nil)).Elem(),
})
} }
// ConnectionPolicies govern the establishment of TLS connections. It is // ConnectionPolicies govern the establishment of TLS connections. It is

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"reflect"
"runtime/debug" "runtime/debug"
"sync" "sync"
"time" "time"
@ -28,6 +29,12 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
func init() {
caddy.RegisterType("tls.stek", []reflect.Type{
reflect.TypeOf((*STEKProvider)(nil)).Elem(),
})
}
// SessionTicketService configures and manages TLS session tickets. // SessionTicketService configures and manages TLS session tickets.
type SessionTicketService struct { type SessionTicketService struct {
// KeySource is the method by which Caddy produces or obtains // KeySource is the method by which Caddy produces or obtains

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"reflect"
"runtime/debug" "runtime/debug"
"sync" "sync"
"time" "time"
@ -35,6 +36,10 @@ import (
func init() { func init() {
caddy.RegisterModule(TLS{}) caddy.RegisterModule(TLS{})
caddy.RegisterModule(AutomateLoader{}) caddy.RegisterModule(AutomateLoader{})
caddy.RegisterType("tls.certificates", []reflect.Type{
reflect.TypeOf((*CertificateLoader)(nil)).Elem(),
})
} }
var ( var (
@ -652,6 +657,11 @@ func (AutomateLoader) CaddyModule() caddy.ModuleInfo {
} }
} }
// LoadCertificates is a stub so AutomateLoader can implement CertificateLoader
func (AutomateLoader) LoadCertificates() ([]Certificate, error) {
return nil, nil
}
// CertCacheOptions configures the certificate cache. // CertCacheOptions configures the certificate cache.
type CertCacheOptions struct { type CertCacheOptions struct {
// Maximum number of certificates to allow in the // Maximum number of certificates to allow in the

35
types.go Normal file
View file

@ -0,0 +1,35 @@
package caddy
import (
"fmt"
"reflect"
)
var namespaceTypes map[string][]reflect.Type = make(map[string][]reflect.Type)
func RegisterType(namespace string, types []reflect.Type) {
if _, ok := namespaceTypes[namespace]; ok {
panic("namespace is already registered")
}
namespaceTypes[namespace] = types
}
// NamespaceTypes returns a copy of Caddy's namespace->type registry
func NamespaceTypes() map[string][]reflect.Type {
copy := make(map[string][]reflect.Type)
for namespace, typeSlice := range namespaceTypes {
copy[namespace] = typeSlice
}
return copy
}
// ConformsToNamespace validates the given module implements all the mandatory types of a given namespace
func ConformsToNamespace(mod Module, namespace string) (bool, error) {
modType := reflect.TypeOf(mod)
for _, t := range namespaceTypes[namespace] {
if !modType.Implements(t) {
return false, fmt.Errorf("%s does not implement %s", modType, t)
}
}
return true, nil
}