1package file
2
3import (
4	"context"
5	"encoding/json"
6	"os"
7	"path/filepath"
8
9	"github.com/BurntSushi/toml"
10	"github.com/heetch/confita/backend"
11	"github.com/pkg/errors"
12	"gopkg.in/yaml.v2"
13)
14
15// Backend that loads a configuration from a file.
16// It supports json and yaml formats.
17type Backend struct {
18	path     string
19	name     string
20	optional bool
21}
22
23// NewBackend creates a configuration loader that loads from a file.
24// The content will get decoded based on the file extension.
25// If optional parameter is set to true, calling Unmarshal won't return an error if the file doesn't exist.
26func NewBackend(path string) *Backend {
27	name := filepath.Ext(path)
28	if name != "" {
29		name = name[1:]
30	}
31
32	return &Backend{
33		path: path,
34		name: name,
35	}
36}
37
38// NewOptionalBackend implementation is exactly the same as NewBackend except that
39// if the file is not found, backend.ErrNotFound will be returned.
40func NewOptionalBackend(path string) *Backend {
41	name := filepath.Ext(path)
42	if name != "" {
43		name = name[1:]
44	}
45
46	return &Backend{
47		path:     path,
48		name:     name,
49		optional: true,
50	}
51}
52
53// Unmarshal takes a struct pointer and unmarshals the file into it,
54// using either json or yaml based on the file extention.
55func (b *Backend) Unmarshal(ctx context.Context, to interface{}) error {
56	f, err := os.Open(b.path)
57	if err != nil {
58		if b.optional {
59			return backend.ErrNotFound
60		}
61		return errors.Wrapf(err, "failed to open file at path \"%s\"", b.path)
62	}
63	defer f.Close()
64
65	switch ext := filepath.Ext(b.path); ext {
66	case ".json":
67		err = json.NewDecoder(f).Decode(to)
68	case ".yml":
69		fallthrough
70	case ".yaml":
71		err = yaml.NewDecoder(f).Decode(to)
72	case ".toml":
73		_, err = toml.DecodeReader(f, to)
74	default:
75		err = errors.Errorf("unsupported extension \"%s\"", ext)
76	}
77
78	return errors.Wrapf(err, "failed to decode file \"%s\"", b.path)
79}
80
81// Get is not implemented.
82func (b *Backend) Get(ctx context.Context, key string) ([]byte, error) {
83	return nil, errors.New("not implemented")
84}
85
86// Name returns the type of the file.
87func (b *Backend) Name() string {
88	return b.name
89}
90