11package v1
22
33import (
4+ "errors"
45 "fmt"
56 "os"
67 "path/filepath"
@@ -22,11 +23,8 @@ type validationError struct {
2223 detail string
2324}
2425
25- func (e ValidationError ) Error () string {
26- if len (e .HTTPErrorList .Errors ) > 0 {
27- return e .HTTPErrorList .Errors [0 ].Detail
28- }
29- return "validation error"
26+ func (ve validationError ) Error () string {
27+ return ve .detail
3028}
3129
3230func newValidationError (title , detail string ) error {
@@ -50,115 +48,73 @@ func parseOctalMode(modeStr *string) *os.FileMode {
5048}
5149
5250// parseFileUser extracts user from File union type
53- func parseFileUser (fileUser * File_User ) interface {} {
51+ func parseFileUser (fileUser * File_User ) any {
5452 if fileUser == nil {
5553 return nil
5654 }
5755
5856 if userStr , err := fileUser .AsFileUser0 (); err == nil {
5957 return userStr
60- } else if userInt , err := fileUser .AsFileUser1 (); err == nil {
58+ }
59+ if userInt , err := fileUser .AsFileUser1 (); err == nil {
6160 return userInt
6261 }
6362 return nil
6463}
6564
6665// parseFileGroup extracts group from File union type
67- func parseFileGroup (fileGroup * File_Group ) interface {} {
66+ func parseFileGroup (fileGroup * File_Group ) any {
6867 if fileGroup == nil {
6968 return nil
7069 }
7170
7271 if groupStr , err := fileGroup .AsFileGroup0 (); err == nil {
7372 return groupStr
74- } else if groupInt , err := fileGroup .AsFileGroup1 (); err == nil {
73+ }
74+ if groupInt , err := fileGroup .AsFileGroup1 (); err == nil {
7575 return groupInt
7676 }
7777 return nil
7878}
7979
8080// parseDirectoryUser extracts user from Directory union type
81- func parseDirectoryUser (dirUser * Directory_User ) interface {} {
81+ func parseDirectoryUser (dirUser * Directory_User ) any {
8282 if dirUser == nil {
8383 return nil
8484 }
8585
8686 if userStr , err := dirUser .AsDirectoryUser0 (); err == nil {
8787 return userStr
88- } else if userInt , err := dirUser .AsDirectoryUser1 (); err == nil {
88+ }
89+ if userInt , err := dirUser .AsDirectoryUser1 (); err == nil {
8990 return userInt
9091 }
9192 return nil
9293}
9394
9495// parseDirectoryGroup extracts group from Directory union type
95- func parseDirectoryGroup (dirGroup * Directory_Group ) interface {} {
96+ func parseDirectoryGroup (dirGroup * Directory_Group ) any {
9697 if dirGroup == nil {
9798 return nil
9899 }
99100
100101 if groupStr , err := dirGroup .AsDirectoryGroup0 (); err == nil {
101102 return groupStr
102- } else if groupInt , err := dirGroup .AsDirectoryGroup1 (); err == nil {
103- return groupInt
104- }
105- return nil
106- }
107-
108- // BlueprintValidator interface for the Chain of Responsibility pattern
109- type BlueprintValidator interface {
110- Validate (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error
111- }
112-
113- // ValidationChain manages a chain of blueprint validators
114- type ValidationChain struct {
115- validators []BlueprintValidator
116- }
117-
118- // Validate executes all validators in the chain and collects all errors
119- func (vc * ValidationChain ) Validate (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error {
120- var allErrors []HTTPError
121-
122- for _ , validator := range vc .validators {
123- if err := validator .Validate (ctx , request , existingUsers ); err != nil {
124- if validationErr , ok := err .(ValidationError ); ok {
125- // Collect all errors from this validator
126- allErrors = append (allErrors , validationErr .HTTPErrorList .Errors ... )
127- } else {
128- // Handle unexpected error types
129- allErrors = append (allErrors , HTTPError {
130- Title : "Validation Error" ,
131- Detail : err .Error (),
132- })
133- }
134- }
135103 }
136-
137- if len (allErrors ) > 0 {
138- return ValidationError {
139- HTTPErrorList : HTTPErrorList {
140- Errors : allErrors ,
141- },
142- }
104+ if groupInt , err := dirGroup .AsDirectoryGroup1 (); err == nil {
105+ return groupInt
143106 }
144-
145107 return nil
146108}
147109
148- // NameValidator validates blueprint names
149- type NameValidator struct {}
150-
151- func (nv * NameValidator ) Validate (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error {
110+ func checkNameRule (request * CreateBlueprintRequest ) error {
152111 if ! blueprintNameRegex .MatchString (request .Name ) {
153- return newValidationError ("Invalid blueprint name" , blueprintInvalidNameDetail )
112+ return newValidationError ("blueprint name rule violation " , blueprintInvalidNameDetail )
154113 }
155114 return nil
156115}
157116
158- // UserValidator validates blueprint users
159- type UserValidator struct {}
160-
161- func (uv * UserValidator ) Validate (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error {
117+ func checkUserRule (request * CreateBlueprintRequest , existingUsers []User ) error {
162118 users := request .Customizations .Users
163119 if users == nil {
164120 return nil
@@ -173,23 +129,20 @@ func (uv *UserValidator) Validate(ctx echo.Context, request *CreateBlueprintRequ
173129 }
174130
175131 if err != nil {
176- return newValidationError ("Invalid user" , err .Error ())
132+ return newValidationError ("user rule violation " , err .Error ())
177133 }
178134 }
179135 return nil
180136}
181137
182- // FileValidator validates blueprint file customizations
183- type FileValidator struct {}
184-
185- func (fv * FileValidator ) Validate (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error {
138+ func checkFileRule (request * CreateBlueprintRequest ) error {
186139 files := request .Customizations .Files
187140 if files == nil {
188141 return nil
189142 }
190143
144+ var errs []error
191145 for _ , file := range * files {
192- // Convert API types to fsnode types for validation
193146 mode := parseOctalMode (file .Mode )
194147 user := parseFileUser (file .User )
195148 group := parseFileGroup (file .Group )
@@ -199,26 +152,26 @@ func (fv *FileValidator) Validate(ctx echo.Context, request *CreateBlueprintRequ
199152 data = []byte (* file .Data )
200153 }
201154
202- // Use fsnode.NewFile for validation - this handles all path, mode, user, group validation
203155 _ , err := fsnode .NewFile (file .Path , mode , user , group , data )
204156 if err != nil {
205- return newValidationError ("Invalid file customization" , fmt .Sprintf ("file %q: %s" , file .Path , err .Error ()))
157+ errs = append (errs , newValidationError (
158+ "file rule violation" ,
159+ fmt .Sprintf ("file %q: %s" , file .Path , err .Error ()),
160+ ))
206161 }
207162 }
208- return nil
209- }
210163
211- // DirectoryValidator validates blueprint directory customizations
212- type DirectoryValidator struct { }
164+ return errors . Join ( errs ... )
165+ }
213166
214- func ( dv * DirectoryValidator ) Validate ( ctx echo. Context , request * CreateBlueprintRequest , existingUsers [] User ) error {
167+ func checkDirectoryRule ( request * CreateBlueprintRequest ) error {
215168 directories := request .Customizations .Directories
216169 if directories == nil {
217170 return nil
218171 }
219172
173+ var errs []error
220174 for _ , dir := range * directories {
221- // Convert API types to fsnode types for validation
222175 mode := parseOctalMode (dir .Mode )
223176 user := parseDirectoryUser (dir .User )
224177 group := parseDirectoryGroup (dir .Group )
@@ -228,60 +181,61 @@ func (dv *DirectoryValidator) Validate(ctx echo.Context, request *CreateBlueprin
228181 ensureParents = * dir .EnsureParents
229182 }
230183
231- // Use fsnode.NewDirectory for validation - this handles all path, mode, user, group validation
232184 _ , err := fsnode .NewDirectory (dir .Path , mode , user , group , ensureParents )
233185 if err != nil {
234- return newValidationError ("Invalid directory customization" , fmt .Sprintf ("directory %q: %s" , dir .Path , err .Error ()))
186+ errs = append (errs , newValidationError (
187+ "directory rule violation" ,
188+ fmt .Sprintf ("directory %q: %s" , dir .Path , err .Error ()),
189+ ))
235190 }
236191 }
237- return nil
238- }
239192
240- // FilesystemValidator validates blueprint filesystem customizations
241- type FilesystemValidator struct { }
193+ return errors . Join ( errs ... )
194+ }
242195
243- func ( fsv * FilesystemValidator ) Validate ( ctx echo. Context , request * CreateBlueprintRequest , existingUsers [] User ) error {
196+ func checkFilesystemRule ( request * CreateBlueprintRequest ) error {
244197 filesystem := request .Customizations .Filesystem
245198 if filesystem == nil {
246199 return nil
247200 }
248201
202+ var errs []error
249203 for _ , fs := range * filesystem {
250- // Use the same path validation logic as fsnode (following library patterns)
251204 if fs .Mountpoint == "" {
252- return newValidationError ("Invalid filesystem customization" , "mountpoint must not be empty" )
253- }
254- if fs .Mountpoint [0 ] != '/' {
255- return newValidationError ("Invalid filesystem customization" , fmt .Sprintf ("mountpoint %q must be absolute" , fs .Mountpoint ))
256- }
257- if fs .Mountpoint != filepath .Clean (fs .Mountpoint ) {
258- return newValidationError ("Invalid filesystem customization" , fmt .Sprintf ("mountpoint %q must be canonical" , fs .Mountpoint ))
205+ errs = append (errs , newValidationError (
206+ "filesystem rule violation" ,
207+ "mountpoint must not be empty" ,
208+ ))
209+ } else if fs .Mountpoint [0 ] != '/' {
210+ errs = append (errs , newValidationError (
211+ "filesystem rule violation" ,
212+ fmt .Sprintf ("mountpoint %q must be absolute" , fs .Mountpoint ),
213+ ))
214+ } else if fs .Mountpoint != filepath .Clean (fs .Mountpoint ) {
215+ errs = append (errs , newValidationError (
216+ "filesystem rule violation" ,
217+ fmt .Sprintf ("mountpoint %q must be canonical" , fs .Mountpoint ),
218+ ))
259219 }
260220
261- // Validate minimum size is reasonable
262- if fs .MinSize > 0 && fs .MinSize < 1024 * 1024 { // 1MB minimum
263- return newValidationError ("Invalid filesystem customization" , fmt .Sprintf ("mountpoint %q minimum size must be at least 1MB" , fs .Mountpoint ))
221+ if fs .MinSize > 0 && fs .MinSize < 1024 * 1024 {
222+ errs = append (errs , newValidationError (
223+ "filesystem rule violation" ,
224+ fmt .Sprintf ("mountpoint %q minimum size must be at least 1MB" , fs .Mountpoint ),
225+ ))
264226 }
265227 }
266- return nil
267- }
268228
269- // NewBlueprintValidationChain creates a new validation chain with all validators
270- func NewBlueprintValidationChain () * ValidationChain {
271- return & ValidationChain {
272- validators : []BlueprintValidator {
273- & NameValidator {},
274- & UserValidator {},
275- & FileValidator {},
276- & DirectoryValidator {},
277- & FilesystemValidator {},
278- },
279- }
229+ return errors .Join (errs ... )
280230}
281231
282- // ValidateBlueprintRequest performs common validation for blueprint requests
283- // using the Chain of Responsibility pattern
284- func ValidateBlueprintRequest (ctx echo.Context , blueprintRequest * CreateBlueprintRequest , existingUsers []User ) error {
285- chain := NewBlueprintValidationChain ()
286- return chain .Validate (ctx , blueprintRequest , existingUsers )
232+ // CheckBlueprintRules performs common rule checking for blueprint requests
233+ func CheckBlueprintRules (ctx echo.Context , request * CreateBlueprintRequest , existingUsers []User ) error {
234+ return errors .Join (
235+ checkNameRule (request ),
236+ checkUserRule (request , existingUsers ),
237+ checkFileRule (request ),
238+ checkDirectoryRule (request ),
239+ checkFilesystemRule (request ),
240+ )
287241}
0 commit comments