// Copyright 2024 Matthew Rich . All rights reserved. package config import ( "errors" "encoding/json" "fmt" "gopkg.in/yaml.v3" "io" "log/slog" _ "net/url" "github.com/sters/yaml-diff/yamldiff" "strings" "decl/internal/codec" _ "context" ) var ( ErrConfigUndefinedName = errors.New("Config block is missing a defined name") ) type ConfigNamesMap[Value any] map[string]Value type Document struct { names ConfigNamesMap[*Block] ConfigBlocks []Block `json:"configurations" yaml:"configurations"` } func NewDocument() *Document { return &Document{ names: make(ConfigNamesMap[*Block]) } } func (d *Document) Filter(filter ConfigurationSelector) []*Block { configurations := make([]*Block, 0, len(d.ConfigBlocks)) for i := range d.ConfigBlocks { filterConfig := &d.ConfigBlocks[i] if filter == nil || filter(filterConfig) { configurations = append(configurations, &d.ConfigBlocks[i]) } } return configurations } func (d *Document) Clone() *Document { clone := NewDocument() clone.ConfigBlocks = make([]Block, len(d.ConfigBlocks)) for i, res := range d.ConfigBlocks { clone.ConfigBlocks[i] = *res.Clone() } return clone } func (d *Document) Load(r io.Reader) error { c := codec.NewYAMLDecoder(r) return c.Decode(d); } func (d *Document) Validate() error { jsonDocument, jsonErr := d.JSON() if jsonErr == nil { s := NewSchema("document") err := s.Validate(string(jsonDocument)) if err != nil { return err } } return nil } func (d *Document) Configurations() []Block { return d.ConfigBlocks } func (d *Document) Generate(w io.Writer) error { e := codec.NewYAMLEncoder(w) err := e.Encode(d); if err == nil { return e.Close() } e.Close() return err } func (d *Document) Append(doc *Document) { if doc != nil { for i := range doc.ConfigBlocks { slog.Info("Document.Append()", "doc", doc, "block", doc.ConfigBlocks[i], "targetdoc", d) d.AddConfigurationBlock(doc.ConfigBlocks[i].Name, doc.ConfigBlocks[i].Type, doc.ConfigBlocks[i].Values) } } } func (d *Document) AddConfigurationBlock(configurationName string, configurationType TypeName, configuration Configuration) { cfg := NewBlock() cfg.Name = configurationName cfg.Type = configurationType cfg.Values = configuration d.names[cfg.Name] = cfg d.ConfigBlocks = append(d.ConfigBlocks, *cfg) } func (d *Document) AddConfiguration(uri string) error { cfg := NewBlock() if e := cfg.SetURI(uri); e != nil { return e } if cfg.Name == "" { return ErrConfigUndefinedName } d.names[cfg.Name] = cfg d.ConfigBlocks = append(d.ConfigBlocks, *cfg) return nil } func (d *Document) Has(name string) bool { _, ok := d.names[name] return ok } func (d *Document) Get(name string) *Block { return d.names[name] } func (d *Document) JSON() ([]byte, error) { return json.Marshal(d) } func (d *Document) YAML() ([]byte, error) { return yaml.Marshal(d) } func (d *Document) IndexName() error { for _, b := range d.ConfigBlocks { d.names[b.Name] = &b } return nil } func (d *Document) UnmarshalYAML(value *yaml.Node) error { documentBlocks := struct { ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"` }{ ConfigBlocks: &d.ConfigBlocks } if unmarshalDocumentErr := value.Decode(documentBlocks); unmarshalDocumentErr != nil { return unmarshalDocumentErr } return d.IndexName() } func (d *Document) UnmarshalJSON(data []byte) error { documentBlocks := struct { ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"` }{ ConfigBlocks: &d.ConfigBlocks } if unmarshalDocumentErr := json.Unmarshal(data, &documentBlocks); unmarshalDocumentErr != nil { return unmarshalDocumentErr } return d.IndexName() } func (d *Document) Diff(with *Document, output io.Writer) (returnOutput string, diffErr error) { defer func() { if r := recover(); r != nil { returnOutput = "" diffErr = fmt.Errorf("%s", r) } }() slog.Info("Document.Diff()") opts := []yamldiff.DoOptionFunc{} if output == nil { output = &strings.Builder{} } ydata, yerr := d.YAML() if yerr != nil { return "", yerr } yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata)) if yamlDiffErr != nil { return "", yamlDiffErr } wdata,werr := with.YAML() if werr != nil { return "", werr } withDiff,withDiffErr := yamldiff.Load(string(wdata)) if withDiffErr != nil { return "", withDiffErr } for _,docDiffResults := range yamldiff.Do(yamlDiff, withDiff, opts...) { slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump()) _,e := output.Write([]byte(docDiffResults.Dump())) if e != nil { return "", e } } slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata) if stringOutput, ok := output.(*strings.Builder); ok { return stringOutput.String(), nil } return "", nil }