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,172 @@ 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.
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 {
37+ Key string `toml:"key"`
38+ File string `toml:"file,omitempty"`
39+ Data string `toml:"data,omitempty"`
40+ Metadata string `toml:"metadata,omitempty"`
41+ }
42+
43+ // KVStoreExternalFile represents the external key/value store,
44+ // which must have both a file and a format.
45+ type KVStoreExternalFile struct {
46+ File string `toml:"file"`
47+ Format string `toml:"format"`
48+ }
49+
50+ // LocalKVStore represents a kv_store to be mocked by the local testing server.
51+ // It is a union type and can either be an array of KVStoreArrayEntry or a single KVStoreExternalFile.
52+ // The IsArray flag is used to preserve the original input style.
2853type LocalKVStore struct {
54+ IsArray bool `toml:"-"`
55+ Array []KVStoreArrayEntry `toml:"-"`
56+ External * KVStoreExternalFile `toml:"-"`
57+ }
58+
59+ // LocalKVStoreMap is a map of kv_store names to the local kv_store representation.
60+ type LocalKVStoreMap map [string ]LocalKVStore
61+
62+ // UnmarshalTOML performs custom unmarshalling of TOML data for LocalKVStoreMap.
63+ func (m * LocalKVStoreMap ) UnmarshalTOML (v any ) error {
64+ raw , ok := v .(map [string ]any )
65+ if ! ok {
66+ return fmt .Errorf ("expected kv_stores to be a TOML table" )
67+ }
68+
69+ result := make (LocalKVStoreMap )
70+
71+ for key , val := range raw {
72+ switch typed := val .(type ) {
73+ case []any :
74+ var entries []KVStoreArrayEntry
75+ for _ , item := range typed {
76+ obj , ok := item .(map [string ]any )
77+ if ! ok {
78+ return fmt .Errorf ("invalid item in array for key %q" , key )
79+ }
80+ var arrayEntry KVStoreArrayEntry
81+ if err := decodeTOMLMap (obj , & arrayEntry ); err != nil {
82+ return fmt .Errorf ("decode failed for array item in key %q: %w" , key , err )
83+ }
84+ entries = append (entries , arrayEntry )
85+ }
86+ result [key ] = LocalKVStore {
87+ IsArray : true ,
88+ Array : entries ,
89+ }
90+
91+ case map [string ]any :
92+ file , hasFile := typed ["file" ].(string )
93+ format , hasFormat := typed ["format" ].(string )
94+
95+ if ! hasFile || ! hasFormat {
96+ return fmt .Errorf ("key %q must have both file and format" , key )
97+ }
98+ result [key ] = LocalKVStore {
99+ IsArray : false ,
100+ External : & KVStoreExternalFile {
101+ File : file ,
102+ Format : format ,
103+ },
104+ }
105+
106+ default :
107+ return fmt .Errorf ("unsupported value type for key %q: %T" , key , typed )
108+ }
109+ }
110+
111+ * m = result
112+ return nil
113+ }
114+
115+ // SecretStoreArrayEntry represents an array-based key/value store entries.
116+ // It expects a key plus either a data or file field.
117+ type SecretStoreArrayEntry struct {
29118 Key string `toml:"key"`
30119 File string `toml:"file,omitempty"`
31120 Data string `toml:"data,omitempty"`
32121}
33122
123+ // SecretStoreExternalFile represents the external key/value store,
124+ // which must have both a file and a format.
125+ type SecretStoreExternalFile struct {
126+ File string `toml:"file"`
127+ Format string `toml:"format"`
128+ }
129+
34130// LocalSecretStore represents a secret_store to be mocked by the local testing server.
131+ // It is a union type and can either be an array of SecretStoreArrayEntry or a single SecretStoreExternalFile.
132+ // The IsArray flag is used to preserve the original input style.
35133type LocalSecretStore struct {
36- Key string `toml:"key"`
37- File string `toml:"file,omitempty"`
38- Data string `toml:"data,omitempty"`
134+ IsArray bool `toml:"-"`
135+ Array []SecretStoreArrayEntry `toml:"-"`
136+ External * SecretStoreExternalFile `toml:"-"`
137+ }
138+
139+ // LocalSecretStoreMap is a map of secret_store names to the local secret_store representation.
140+ type LocalSecretStoreMap map [string ]LocalSecretStore
141+
142+ // UnmarshalTOML performs custom unmarshalling of TOML data for LocalSecretStoreMap.
143+ func (m * LocalSecretStoreMap ) UnmarshalTOML (v any ) error {
144+ raw , ok := v .(map [string ]any )
145+ if ! ok {
146+ return fmt .Errorf ("expected secret_stores to be a TOML table" )
147+ }
148+
149+ result := make (LocalSecretStoreMap )
150+
151+ for key , val := range raw {
152+ switch typed := val .(type ) {
153+ case []any :
154+ var entries []SecretStoreArrayEntry
155+ for _ , item := range typed {
156+ obj , ok := item .(map [string ]any )
157+ if ! ok {
158+ return fmt .Errorf ("invalid item in array for key %q" , key )
159+ }
160+ var arrayEntry SecretStoreArrayEntry
161+ if err := decodeTOMLMap (obj , & arrayEntry ); err != nil {
162+ return fmt .Errorf ("decode failed for array item in key %q: %w" , key , err )
163+ }
164+ entries = append (entries , arrayEntry )
165+ }
166+ result [key ] = LocalSecretStore {
167+ IsArray : true ,
168+ Array : entries ,
169+ }
170+
171+ case map [string ]any :
172+ file , hasFile := typed ["file" ].(string )
173+ format , hasFormat := typed ["format" ].(string )
174+
175+ if ! hasFile || ! hasFormat {
176+ return fmt .Errorf ("key %q must have both file and format" , key )
177+ }
178+ result [key ] = LocalSecretStore {
179+ IsArray : false ,
180+ External : & SecretStoreExternalFile {
181+ File : file ,
182+ Format : format ,
183+ },
184+ }
185+
186+ default :
187+ return fmt .Errorf ("unsupported value type for key %q: %T" , key , typed )
188+ }
189+ }
190+
191+ * m = result
192+ return nil
193+ }
194+
195+ func decodeTOMLMap (m map [string ]any , out any ) error {
196+ buf := new (bytes.Buffer )
197+ enc := toml .NewEncoder (buf )
198+ if err := enc .Encode (m ); err != nil {
199+ return err
200+ }
201+ return toml .NewDecoder (buf ).Decode (out )
39202}
0 commit comments