summaryrefslogtreecommitdiff
path: root/data/datasource_merge.go
blob: b0d8b6bdb8c90c85066dfe51a2361a310f7959d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package data

import (
	"context"
	"fmt"
	"strings"

	"github.com/hairyhenderson/gomplate/v4/coll"
	"github.com/hairyhenderson/gomplate/v4/internal/datafs"
)

// readMerge demultiplexes a `merge:` datasource. The 'args' parameter currently
// has no meaning for this source.
//
// URI format is 'merge:<source 1>|<source 2>[|<source n>...]' where `<source #>`
// is a supported URI or a pre-defined alias name.
//
// Query strings and fragments are interpreted relative to the merged data, not
// the source data. To merge datasources with query strings or fragments, define
// separate sources first and specify the alias names. HTTP headers are also not
// supported directly.
func (d *Data) readMerge(ctx context.Context, source *Source, _ ...string) ([]byte, error) {
	opaque := source.URL.Opaque
	parts := strings.Split(opaque, "|")
	if len(parts) < 2 {
		return nil, fmt.Errorf("need at least 2 datasources to merge")
	}
	data := make([]map[string]interface{}, len(parts))
	for i, part := range parts {
		// supports either URIs or aliases
		subSource, err := d.lookupSource(part)
		if err != nil {
			// maybe it's a relative filename?
			u, uerr := datafs.ParseSourceURL(part)
			if uerr != nil {
				return nil, uerr
			}
			subSource = &Source{
				Alias: part,
				URL:   u,
			}
		}
		subSource.inherit(source)

		b, err := d.readSource(ctx, subSource)
		if err != nil {
			return nil, fmt.Errorf("couldn't read datasource '%s': %w", part, err)
		}

		mimeType, err := subSource.mimeType("")
		if err != nil {
			return nil, fmt.Errorf("failed to read datasource %s: %w", subSource.URL, err)
		}

		data[i], err = parseMap(mimeType, string(b))
		if err != nil {
			return nil, err
		}
	}

	// Merge the data together
	b, err := mergeData(data)
	if err != nil {
		return nil, err
	}

	source.mediaType = yamlMimetype
	return b, nil
}

func mergeData(data []map[string]interface{}) (out []byte, err error) {
	dst := data[0]
	data = data[1:]

	dst, err = coll.Merge(dst, data...)
	if err != nil {
		return nil, err
	}

	s, err := ToYAML(dst)
	if err != nil {
		return nil, err
	}
	return []byte(s), nil
}

func parseMap(mimeType, data string) (map[string]interface{}, error) {
	datum, err := parseData(mimeType, data)
	if err != nil {
		return nil, err
	}
	var m map[string]interface{}
	switch datum := datum.(type) {
	case map[string]interface{}:
		m = datum
	default:
		return nil, fmt.Errorf("unexpected data type '%T' for datasource (type %s); merge: can only merge maps", datum, mimeType)
	}
	return m, nil
}