// Copyright 2024 Matthew Rich . All rights reserved. package types import ( "errors" "fmt" "net/url" "strings" "path/filepath" "log/slog" _ "runtime/debug" ) /* The `types` package provides a generic method of registering a type factory. */ var ( ErrUnknownType = errors.New("Unknown type") ErrInvalidProduct = errors.New("Invalid product") ) type Factory[Product comparable] func(*url.URL) Product type RegistryTypeMap[Product comparable] map[string]Factory[Product] //type Name[Registry any] string //`json:"type"` type Types[Product comparable] struct { registry RegistryTypeMap[Product] contentTypes RegistryTypeMap[Product] } func New[Product comparable]() *Types[Product] { return &Types[Product]{registry: make(map[string]Factory[Product]), contentTypes: make(map[string]Factory[Product])} } func (t *Types[Product]) Register(names []string, factory Factory[Product]) { for _,name := range names { t.registry[name] = factory } } func (t *Types[Product]) RegisterContentType(contenttypes []string, factory Factory[Product]) { for _,name := range contenttypes { t.contentTypes[name] = factory } } func (t *Types[Product]) FromExtension(path string) (Factory[Product], error) { elements := strings.Split(path, ".") numberOfElements := len(elements) slog.Info("Types[Product].FromExtension()", "path", path, "elements", elements, "types", t.contentTypes, "numberOfElements", numberOfElements) if numberOfElements >= 2 { // slog.Info("Types[Product].FromExtension()", "path", path, "elements", elements, "types", t.contentTypes, "stacktrace", string(debug.Stack())) if numberOfElements > 2 { ext := strings.Join(elements[numberOfElements - 2: numberOfElements], ".") // slog.Info("Types[Product].FromExtension() - Lookup", "ext", ext, "stacktrace", string(debug.Stack())) slog.Info("Types[Product].FromExtension() - Lookup", "ext", ext, "types", t) if src := t.GetContentType(ext); src != nil { return src, nil } } slog.Info("Types[Product].FromExtension() - Lookup", "ext", elements[numberOfElements - 1], "types", t.contentTypes) if src := t.GetContentType(elements[numberOfElements - 1]); src != nil { return src, nil } } return nil, fmt.Errorf("%w: %s", ErrUnknownType, path) } func (t *Types[Product]) New(uri string) (result Product, err error) { u, e := url.Parse(uri) if u == nil || e != nil { err = fmt.Errorf("%w: %s %s", ErrUnknownType, e, uri) return } return t.NewFromParsedURI(u) } func (t *Types[Product]) NewFromParsedURI(u *url.URL) (result Product, err error) { if u.Scheme == "" { u.Scheme = "file" } path := filepath.Join(u.Hostname(), u.Path) if len(path) > 0 { if d, lookupErr := t.FromExtension(filepath.Base(path)); d != nil { return d(u), lookupErr } else { slog.Info("Types[Product].NewFromParsedURI() - FromExtension()", "uri", u, "path", path, "error", lookupErr) //, "stacktrace", string(debug.Stack())) } } if r, ok := t.registry[u.Scheme]; ok { if result = r(u); result != any(nil) { return result, nil } else { return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, u.String()) } } err = fmt.Errorf("%w: %s", ErrUnknownType, u.Scheme) return } func (t *Types[Product]) Has(typename string) bool { if _, ok := t.registry[typename]; ok { return true } return false } func (t *Types[Product]) HasContentType(contenttype string) bool { if _, ok := t.contentTypes[contenttype]; ok { return true } return false } func (t *Types[Product]) GetContentType(contenttype string) Factory[Product] { if d, ok := t.contentTypes[contenttype]; ok { return d } return nil } /* func (n *Name[Registry]) UnmarshalJSON(b []byte) error { ProductTypeName := strings.Trim(string(b), "\"") if Registry.Has(ProductTypeName) { *n = Name[Registry](ProductTypeName) return nil } return fmt.Errorf("%w: %s", ErrUnknownType, ProductTypeName) } */