Skip to content

Commit f012b49

Browse files
committed
refactor(node): reduce cyclomatic complexity in validator
- Extract ValidateInputs helper functions to reduce complexity from 22 to <10 - applyDefaultValues: Apply default values for optional parameters - checkRequiredParameters: Validate required parameters presence - validateAllParameters: Validate types and constraints - validateConstraints: Validate pattern, enum, and range constraints - Extract validateType helper functions to reduce complexity from 16 to <5 - validateStringType: String type validation - validateIntType: Integer type with JSON compatibility - validateFloatType: Float type validation - validateBoolType: Boolean type validation - validateObjectType: Object/map type validation - validateArrayType: Array/slice type validation Resolves: golangci-lint gocyclo errors from GitHub Actions Coverage: All pkg/dsl/node tests passing (84.4%)
1 parent 8d2c6c7 commit f012b49

1 file changed

Lines changed: 156 additions & 100 deletions

File tree

pkg/dsl/node/validator.go

Lines changed: 156 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,38 @@ func validateParamSpec(paramName string, spec ParamSpec) error {
157157
func ValidateInputs(inputs map[string]interface{}, specs map[string]ParamSpec) error {
158158
var errors []ParameterError
159159

160-
// 1. Apply default values for optional parameters
160+
// 1. Apply default values
161+
applyDefaultValues(inputs, specs)
162+
163+
// 2. Check required parameters
164+
errors = append(errors, checkRequiredParameters(inputs, specs)...)
165+
166+
// 3. Validate parameter types and constraints
167+
errors = append(errors, validateAllParameters(inputs, specs)...)
168+
169+
if len(errors) > 0 {
170+
return &InputValidationError{
171+
Errors: errors,
172+
}
173+
}
174+
175+
return nil
176+
}
177+
178+
// applyDefaultValues applies default values for optional parameters.
179+
func applyDefaultValues(inputs map[string]interface{}, specs map[string]ParamSpec) {
161180
for paramName, spec := range specs {
162181
if !spec.Required && spec.Default != nil {
163182
if _, exists := inputs[paramName]; !exists {
164183
inputs[paramName] = spec.Default
165184
}
166185
}
167186
}
187+
}
168188

169-
// 2. Check required parameters
189+
// checkRequiredParameters validates that all required parameters are present.
190+
func checkRequiredParameters(inputs map[string]interface{}, specs map[string]ParamSpec) []ParameterError {
191+
var errors []ParameterError
170192
for paramName, spec := range specs {
171193
if spec.Required {
172194
if _, exists := inputs[paramName]; !exists {
@@ -180,8 +202,12 @@ func ValidateInputs(inputs map[string]interface{}, specs map[string]ParamSpec) e
180202
}
181203
}
182204
}
205+
return errors
206+
}
183207

184-
// 3. Validate parameter types and constraints
208+
// validateAllParameters validates types and constraints for all parameters.
209+
func validateAllParameters(inputs map[string]interface{}, specs map[string]ParamSpec) []ParameterError {
210+
var errors []ParameterError
185211
for paramName, value := range inputs {
186212
spec, exists := specs[paramName]
187213
if !exists {
@@ -195,131 +221,161 @@ func ValidateInputs(inputs map[string]interface{}, specs map[string]ParamSpec) e
195221
continue // Skip constraint validation if type is wrong
196222
}
197223

198-
// Pattern validation (only for string type)
199-
if spec.Pattern != "" && spec.Type == "string" {
200-
if err := validatePattern(paramName, value.(string), spec.Pattern); err != nil {
201-
errors = append(errors, *err)
202-
}
203-
}
224+
// Constraint validations
225+
errors = append(errors, validateConstraints(paramName, value, spec)...)
226+
}
227+
return errors
228+
}
204229

205-
// Enum validation
206-
if len(spec.Enum) > 0 {
207-
if err := validateEnumConstraint(paramName, value, spec.Enum); err != nil {
208-
errors = append(errors, *err)
209-
}
230+
// validateConstraints validates pattern, enum, and range constraints.
231+
func validateConstraints(paramName string, value interface{}, spec ParamSpec) []ParameterError {
232+
var errors []ParameterError
233+
234+
// Pattern validation (only for string type)
235+
if spec.Pattern != "" && spec.Type == "string" {
236+
if err := validatePattern(paramName, value.(string), spec.Pattern); err != nil {
237+
errors = append(errors, *err)
210238
}
239+
}
211240

212-
// Numeric range validation
213-
if (spec.Type == "int" || spec.Type == "float") && (spec.MinValue != nil || spec.MaxValue != nil) {
214-
if err := validateRange(paramName, value, spec.MinValue, spec.MaxValue); err != nil {
215-
errors = append(errors, *err)
216-
}
241+
// Enum validation
242+
if len(spec.Enum) > 0 {
243+
if err := validateEnumConstraint(paramName, value, spec.Enum); err != nil {
244+
errors = append(errors, *err)
217245
}
218246
}
219247

220-
if len(errors) > 0 {
221-
return &InputValidationError{
222-
Errors: errors,
248+
// Numeric range validation
249+
if (spec.Type == "int" || spec.Type == "float") && (spec.MinValue != nil || spec.MaxValue != nil) {
250+
if err := validateRange(paramName, value, spec.MinValue, spec.MaxValue); err != nil {
251+
errors = append(errors, *err)
223252
}
224253
}
225254

226-
return nil
255+
return errors
227256
}
228257

229258
// validateType validates parameter type matches expected type.
230259
// Handles JSON number compatibility (all numbers parsed as float64).
231260
func validateType(paramName string, value interface{}, expectedType string) *ParameterError {
232-
actualType := getGoType(value)
233-
234261
switch expectedType {
235262
case "string":
236-
if _, ok := value.(string); !ok {
237-
return &ParameterError{
238-
ParamName: paramName,
239-
ErrorType: "TypeMismatch",
240-
Expected: "string",
241-
Actual: actualType,
242-
Message: fmt.Sprintf("expected string, got %s", actualType),
243-
}
244-
}
245-
263+
return validateStringType(paramName, value)
246264
case "int":
247-
switch v := value.(type) {
248-
case int, int32, int64:
249-
return nil // Native int types
250-
case float64:
251-
// JSON-parsed numbers, check if it's a whole number
252-
if v == float64(int64(v)) {
253-
return nil // ✅ 30.0 is valid int
254-
}
255-
// Reject floats with decimal parts
256-
return &ParameterError{
257-
ParamName: paramName,
258-
ErrorType: "TypeMismatch",
259-
Expected: "int (whole number)",
260-
Actual: fmt.Sprintf("float64(%v)", v),
261-
Message: fmt.Sprintf("expected integer, got float with decimal: %v", v),
262-
}
263-
default:
264-
return &ParameterError{
265-
ParamName: paramName,
266-
ErrorType: "TypeMismatch",
267-
Expected: "int",
268-
Actual: actualType,
269-
Message: fmt.Sprintf("expected int, got %s", actualType),
270-
}
265+
return validateIntType(paramName, value)
266+
case "float":
267+
return validateFloatType(paramName, value)
268+
case "bool":
269+
return validateBoolType(paramName, value)
270+
case "object":
271+
return validateObjectType(paramName, value)
272+
case "array":
273+
return validateArrayType(paramName, value)
274+
default:
275+
return nil
276+
}
277+
}
278+
279+
// validateStringType validates string type.
280+
func validateStringType(paramName string, value interface{}) *ParameterError {
281+
if _, ok := value.(string); !ok {
282+
return &ParameterError{
283+
ParamName: paramName,
284+
ErrorType: "TypeMismatch",
285+
Expected: "string",
286+
Actual: getGoType(value),
287+
Message: fmt.Sprintf("expected string, got %s", getGoType(value)),
271288
}
289+
}
290+
return nil
291+
}
272292

273-
case "float":
274-
switch value.(type) {
275-
case float32, float64:
276-
return nil
277-
case int, int32, int64:
278-
return nil // ✅ int can be implicitly converted to float
279-
default:
280-
return &ParameterError{
281-
ParamName: paramName,
282-
ErrorType: "TypeMismatch",
283-
Expected: "float",
284-
Actual: actualType,
285-
Message: fmt.Sprintf("expected float, got %s", actualType),
286-
}
293+
// validateIntType validates integer type with JSON compatibility.
294+
func validateIntType(paramName string, value interface{}) *ParameterError {
295+
switch v := value.(type) {
296+
case int, int32, int64:
297+
return nil // Native int types
298+
case float64:
299+
// JSON-parsed numbers, check if it's a whole number
300+
if v == float64(int64(v)) {
301+
return nil // ✅ 30.0 is valid int
287302
}
303+
// Reject floats with decimal parts
304+
return &ParameterError{
305+
ParamName: paramName,
306+
ErrorType: "TypeMismatch",
307+
Expected: "int (whole number)",
308+
Actual: fmt.Sprintf("float64(%v)", v),
309+
Message: fmt.Sprintf("expected integer, got float with decimal: %v", v),
310+
}
311+
default:
312+
return &ParameterError{
313+
ParamName: paramName,
314+
ErrorType: "TypeMismatch",
315+
Expected: "int",
316+
Actual: getGoType(value),
317+
Message: fmt.Sprintf("expected int, got %s", getGoType(value)),
318+
}
319+
}
320+
}
288321

289-
case "bool":
290-
if _, ok := value.(bool); !ok {
291-
return &ParameterError{
292-
ParamName: paramName,
293-
ErrorType: "TypeMismatch",
294-
Expected: "bool",
295-
Actual: actualType,
296-
Message: fmt.Sprintf("expected bool, got %s", actualType),
297-
}
322+
// validateFloatType validates float type.
323+
func validateFloatType(paramName string, value interface{}) *ParameterError {
324+
switch value.(type) {
325+
case float32, float64:
326+
return nil
327+
case int, int32, int64:
328+
return nil // ✅ int can be implicitly converted to float
329+
default:
330+
return &ParameterError{
331+
ParamName: paramName,
332+
ErrorType: "TypeMismatch",
333+
Expected: "float",
334+
Actual: getGoType(value),
335+
Message: fmt.Sprintf("expected float, got %s", getGoType(value)),
298336
}
337+
}
338+
}
299339

300-
case "object":
301-
if _, ok := value.(map[string]interface{}); !ok {
302-
return &ParameterError{
303-
ParamName: paramName,
304-
ErrorType: "TypeMismatch",
305-
Expected: "object (map[string]interface{})",
306-
Actual: actualType,
307-
Message: fmt.Sprintf("expected object, got %s", actualType),
308-
}
340+
// validateBoolType validates boolean type.
341+
func validateBoolType(paramName string, value interface{}) *ParameterError {
342+
if _, ok := value.(bool); !ok {
343+
return &ParameterError{
344+
ParamName: paramName,
345+
ErrorType: "TypeMismatch",
346+
Expected: "bool",
347+
Actual: getGoType(value),
348+
Message: fmt.Sprintf("expected bool, got %s", getGoType(value)),
309349
}
350+
}
351+
return nil
352+
}
310353

311-
case "array":
312-
if _, ok := value.([]interface{}); !ok {
313-
return &ParameterError{
314-
ParamName: paramName,
315-
ErrorType: "TypeMismatch",
316-
Expected: "array ([]interface{})",
317-
Actual: actualType,
318-
Message: fmt.Sprintf("expected array, got %s", actualType),
319-
}
354+
// validateObjectType validates object (map) type.
355+
func validateObjectType(paramName string, value interface{}) *ParameterError {
356+
if _, ok := value.(map[string]interface{}); !ok {
357+
return &ParameterError{
358+
ParamName: paramName,
359+
ErrorType: "TypeMismatch",
360+
Expected: "object (map[string]interface{})",
361+
Actual: getGoType(value),
362+
Message: fmt.Sprintf("expected object, got %s", getGoType(value)),
320363
}
321364
}
365+
return nil
366+
}
322367

368+
// validateArrayType validates array (slice) type.
369+
func validateArrayType(paramName string, value interface{}) *ParameterError {
370+
if _, ok := value.([]interface{}); !ok {
371+
return &ParameterError{
372+
ParamName: paramName,
373+
ErrorType: "TypeMismatch",
374+
Expected: "array ([]interface{})",
375+
Actual: getGoType(value),
376+
Message: fmt.Sprintf("expected array, got %s", getGoType(value)),
377+
}
378+
}
323379
return nil
324380
}
325381

0 commit comments

Comments
 (0)