11package manifest
22
3+ import (
4+ "bytes"
5+ "fmt"
6+
7+ "github.com/pelletier/go-toml"
8+ )
9+
310// LocalServer represents a list of mocked Viceroy resources.
411type LocalServer struct {
5- Backends map [string ]LocalBackend `toml:"backends"`
6- ConfigStores map [string ]LocalConfigStore `toml:"config_stores,omitempty"`
7- KVStores map [ string ][] LocalKVStore `toml:"kv_stores,omitempty"`
8- SecretStores map [ string ][] LocalSecretStore `toml:"secret_stores,omitempty"`
9- ViceroyVersion string `toml:"viceroy_version,omitempty"`
12+ Backends map [string ]LocalBackend `toml:"backends"`
13+ ConfigStores map [string ]LocalConfigStore `toml:"config_stores,omitempty"`
14+ KVStores LocalKVStoreMap `toml:"kv_stores,omitempty"`
15+ SecretStores LocalSecretStoreMap `toml:"secret_stores,omitempty"`
16+ ViceroyVersion string `toml:"viceroy_version,omitempty"`
1017}
1118
1219// LocalBackend represents a backend to be mocked by the local testing server.
@@ -24,16 +31,171 @@ type LocalConfigStore struct {
2431 Contents map [string ]string `toml:"contents,omitempty"`
2532}
2633
27- // LocalKVStore represents an kv_store to be mocked by the local testing server.
28- type LocalKVStore struct {
34+ // KVStoreArrayEntry represents an array-based key/value store entries.
35+ // It expects a key plus either a data or file field.
36+ type KVStoreArrayEntry struct {
2937 Key string `toml:"key"`
3038 File string `toml:"file,omitempty"`
3139 Data string `toml:"data,omitempty"`
3240}
3341
34- // LocalSecretStore represents a secret_store to be mocked by the local testing server.
35- type LocalSecretStore struct {
42+ // KVStoreExternalFile represents the external key/value store,
43+ // which must have both a file and a format.
44+ type KVStoreExternalFile struct {
45+ File string `toml:"file"`
46+ Format string `toml:"format"`
47+ }
48+
49+ // LocalKVStore represents a kv_store to be mocked by the local testing server.
50+ // It is a union type and can either be an array of KVStoreArrayEntry or a single KVStoreExternalFile.
51+ // The IsArray flag is used to preserve the original input style.
52+ type LocalKVStore struct {
53+ IsArray bool `toml:"-"`
54+ Array []KVStoreArrayEntry `toml:"-"`
55+ External * KVStoreExternalFile `toml:"-"`
56+ }
57+
58+ // LocalKVStoreMap is a map of kv_store names to the local kv_store representation.
59+ type LocalKVStoreMap map [string ]LocalKVStore
60+
61+ // UnmarshalTOML performs custom unmarshalling of TOML data for LocalKVStoreMap.
62+ func (m * LocalKVStoreMap ) UnmarshalTOML (v interface {}) error {
63+ raw , ok := v .(map [string ]interface {})
64+ if ! ok {
65+ return fmt .Errorf ("expected kv_stores to be a TOML table" )
66+ }
67+
68+ result := make (LocalKVStoreMap )
69+
70+ for key , val := range raw {
71+ switch typed := val .(type ) {
72+ case []interface {}:
73+ var entries []KVStoreArrayEntry
74+ for _ , item := range typed {
75+ obj , ok := item .(map [string ]interface {})
76+ if ! ok {
77+ return fmt .Errorf ("invalid item in array for key %q" , key )
78+ }
79+ var arrayEntry KVStoreArrayEntry
80+ if err := decodeTOMLMap (obj , & arrayEntry ); err != nil {
81+ return fmt .Errorf ("decode failed for array item in key %q: %w" , key , err )
82+ }
83+ entries = append (entries , arrayEntry )
84+ }
85+ result [key ] = LocalKVStore {
86+ IsArray : true ,
87+ Array : entries ,
88+ }
89+
90+ case map [string ]interface {}:
91+ file , hasFile := typed ["file" ].(string )
92+ format , hasFormat := typed ["format" ].(string )
93+
94+ if ! hasFile || ! hasFormat {
95+ return fmt .Errorf ("key %q must have both file and format" , key )
96+ }
97+ result [key ] = LocalKVStore {
98+ IsArray : false ,
99+ External : & KVStoreExternalFile {
100+ File : file ,
101+ Format : format ,
102+ },
103+ }
104+
105+ default :
106+ return fmt .Errorf ("unsupported value type for key %q: %T" , key , typed )
107+ }
108+ }
109+
110+ * m = result
111+ return nil
112+ }
113+
114+ // SecretStoreArrayEntry represents an array-based key/value store entries.
115+ // It expects a key plus either a data or file field.
116+ type SecretStoreArrayEntry struct {
36117 Key string `toml:"key"`
37118 File string `toml:"file,omitempty"`
38119 Data string `toml:"data,omitempty"`
39120}
121+
122+ // SecretStoreExternalFile represents the external key/value store,
123+ // which must have both a file and a format.
124+ type SecretStoreExternalFile struct {
125+ File string `toml:"file"`
126+ Format string `toml:"format"`
127+ }
128+
129+ // LocalSecretStore represents a secret_store to be mocked by the local testing server.
130+ // It is a union type and can either be an array of SecretStoreArrayEntry or a single SecretStoreExternalFile.
131+ // The IsArray flag is used to preserve the original input style.
132+ type LocalSecretStore struct {
133+ IsArray bool `toml:"-"`
134+ Array []SecretStoreArrayEntry `toml:"-"`
135+ External * SecretStoreExternalFile `toml:"-"`
136+ }
137+
138+ // LocalSecretStoreMap is a map of secret_store names to the local secret_store representation.
139+ type LocalSecretStoreMap map [string ]LocalSecretStore
140+
141+ // UnmarshalTOML performs custom unmarshalling of TOML data for LocalSecretStoreMap.
142+ func (m * LocalSecretStoreMap ) UnmarshalTOML (v interface {}) error {
143+ raw , ok := v .(map [string ]interface {})
144+ if ! ok {
145+ return fmt .Errorf ("expected secret_stores to be a TOML table" )
146+ }
147+
148+ result := make (LocalSecretStoreMap )
149+
150+ for key , val := range raw {
151+ switch typed := val .(type ) {
152+ case []interface {}:
153+ var entries []SecretStoreArrayEntry
154+ for _ , item := range typed {
155+ obj , ok := item .(map [string ]interface {})
156+ if ! ok {
157+ return fmt .Errorf ("invalid item in array for key %q" , key )
158+ }
159+ var arrayEntry SecretStoreArrayEntry
160+ if err := decodeTOMLMap (obj , & arrayEntry ); err != nil {
161+ return fmt .Errorf ("decode failed for array item in key %q: %w" , key , err )
162+ }
163+ entries = append (entries , arrayEntry )
164+ }
165+ result [key ] = LocalSecretStore {
166+ IsArray : true ,
167+ Array : entries ,
168+ }
169+
170+ case map [string ]interface {}:
171+ file , hasFile := typed ["file" ].(string )
172+ format , hasFormat := typed ["format" ].(string )
173+
174+ if ! hasFile || ! hasFormat {
175+ return fmt .Errorf ("key %q must have both file and format" , key )
176+ }
177+ result [key ] = LocalSecretStore {
178+ IsArray : false ,
179+ External : & SecretStoreExternalFile {
180+ File : file ,
181+ Format : format ,
182+ },
183+ }
184+
185+ default :
186+ return fmt .Errorf ("unsupported value type for key %q: %T" , key , typed )
187+ }
188+ }
189+
190+ * m = result
191+ return nil
192+ }
193+
194+ func decodeTOMLMap (m map [string ]interface {}, out interface {}) error {
195+ buf := new (bytes.Buffer )
196+ enc := toml .NewEncoder (buf )
197+ if err := enc .Encode (m ); err != nil {
198+ return err
199+ }
200+ return toml .NewDecoder (buf ).Decode (out )
201+ }
0 commit comments