// Copyright 2024 Matthew Rich . All rights reserved. package folio import ( "encoding/json" "gopkg.in/yaml.v3" "decl/internal/mapper" "decl/internal/data" "decl/internal/transport" "net/url" "log/slog" "errors" "fmt" "io" ) var ( ErrRefMapperMismatch = errors.New("Ref type does not the provided mapper") ) type Ref struct { Uri URI `json:"uri" yaml:"uri"` RefType ReferenceType `json:"type,omitempty" yaml:"type,omitempty"` documentMapper mapper.Store[URI, *Document] resourceMapper mapper.Store[URI, *Declaration] } type decodeRef Ref func NewRef() *Ref { return &Ref { } } func (r *ReferenceType) UnmarshalValue(value string) error { switch value { case string(ReferenceTypeResource), string(ReferenceTypeDocument): *r = ReferenceType(value) default: *r = ReferenceTypeResource //return ErrInvalidReferenceType } return nil } func (r *ReferenceType) UnmarshalJSON(jsonData []byte) error { var s string if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, &s); unmarshalReferenceTypeErr != nil { return unmarshalReferenceTypeErr } return r.UnmarshalValue(s) } func (r *ReferenceType) UnmarshalYAML(value *yaml.Node) error { var s string if err := value.Decode(&s); err != nil { return err } return r.UnmarshalValue(s) } func (r *Ref) UnmarshalValue(value *decodeRef) error { slog.Info("Ref.UnmarshalValue", "decode", value) r.Uri = value.Uri switch value.RefType { case ReferenceTypeResource: r.RefType = value.RefType case ReferenceTypeDocument: r.RefType = value.RefType r.SetMapper(DocumentRegistry.UriMap) default: r.RefType = ReferenceTypeResource //return ErrInvalidReferenceType } slog.Info("Ref.UnmarshalValue", "stored", *r) return nil } func (r *Ref) UnmarshalJSON(jsonData []byte) error { decodeJsonToRef := &decodeRef{} if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, decodeJsonToRef); unmarshalReferenceTypeErr != nil { return unmarshalReferenceTypeErr } return r.UnmarshalValue(decodeJsonToRef) } func (r *Ref) UnmarshalYAML(value *yaml.Node) error { decodeYamlToRef := &decodeRef{} if err := value.Decode(decodeYamlToRef); err != nil { return err } return r.UnmarshalValue(decodeYamlToRef) } func (r *Ref) SetMapper(m any) error { var ok bool switch r.RefType { case ReferenceTypeResource: if r.resourceMapper, ok = m.(mapper.Store[URI, *Declaration]); ! ok { return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType) } case ReferenceTypeDocument: if r.documentMapper, ok = m.(mapper.Store[URI, *Document]); ! ok { return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType) } } return nil } func Dereference[T ReferenceTypes](uri URI, look mapper.Map[URI, T]) T { if uri != "" && look != nil { if v, ok := look.Get(uri); ok { slog.Info("Ref::Dereference()", "value", v, "mapper", look) return v } } return nil } func (r *Ref) Lookup(look any) ContentReadWriter { slog.Info("Ref.Lookup()", "ref", r, "mapper", look) switch r.RefType { case ReferenceTypeResource: if resourceDeclaration := Dereference(r.Uri, look.(mapper.Store[URI, *Declaration])); resourceDeclaration != nil { return resourceDeclaration.GetContentReadWriter() } case ReferenceTypeDocument: if document := Dereference(r.Uri, look.(mapper.Store[URI, *Document])); document != nil { return document.GetContentReadWriter() } } return r.Uri } func (r *Ref) Dereference(look any) any { switch r.RefType { case ReferenceTypeResource: if look == nil { return Dereference(r.Uri, r.resourceMapper) } else { return Dereference(r.Uri, look.(mapper.Store[URI, *Declaration])) } case ReferenceTypeDocument: if look == nil { return Dereference(r.Uri, r.documentMapper) } else { return Dereference(r.Uri, look.(mapper.Store[URI, *Document])) } } return nil } func (r *Ref) DereferenceDefault() any { switch r.RefType { case ReferenceTypeResource: return Dereference(r.Uri, r.resourceMapper) case ReferenceTypeDocument: return Dereference(r.Uri, r.documentMapper) } return nil } func (r *Ref) Parse() data.URIParser { return r.Uri.Parse() } func (r *Ref) Exists() bool { return r.Uri.Exists() } func (r *Ref) ContentReaderStream() (*transport.Reader, error) { return r.Uri.ContentReaderStream() } func (r *Ref) ContentWriterStream() (*transport.Writer, error) { return r.Uri.ContentWriterStream() } func (r *Ref) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) { target := r.DereferenceDefault() if targetContent, ok := target.(data.ContentGetSetter); ok { return targetContent.GetContent(w) } return nil, fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target) } func (r *Ref) SetContent(contentReader io.Reader) error { target := r.DereferenceDefault() if targetContent, ok := target.(data.ContentGetSetter); ok { return targetContent.SetContent(contentReader) } return fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target) } func (r *Ref) Reader() (reader io.ReadCloser, err error) { switch r.RefType { case ReferenceTypeResource: return r.ContentReaderStream() case ReferenceTypeDocument: reader, err = r.GetContent(nil) if reader == nil { return r.ContentReaderStream() } return } return nil, ErrInvalidReferenceType } func (r *Ref) String() string { return r.Uri.String() } func (r *Ref) SetURL(url *url.URL) { r.Uri.SetURL(url) } func (r *Ref) Extension() (string, string) { return r.Uri.Extension() } func (r *Ref) IsEmpty() bool { return r.Uri.IsEmpty() } func (r *Ref) FindIn(s *SearchPath) { r.Uri.FindIn(s) }