@@ -48,6 +48,10 @@ func (r *customStepTypeRegistry) Lookup(name string) (*customStepType, bool) {
4848
4949var customStepTypeNameRegexp = regexp .MustCompile (`^[A-Za-z][A-Za-z0-9_-]*$` )
5050
51+ var customStepRuntimeExpressionRegexp = regexp .MustCompile ("`[^`]+`|\\ $\\ {[^}]+\\ }|\\ $[A-Za-z_][A-Za-z0-9_]*" )
52+
53+ var customStepWholeRuntimeExpressionRegexp = regexp .MustCompile ("^\\ s*(?:`[^`]+`|\\ $\\ {[^}]+\\ }|\\ $[A-Za-z_][A-Za-z0-9_]*)\\ s*$" )
54+
5155var builtinStepTypeNames = map [string ]struct {}{
5256 "agent" : {},
5357 "archive" : {},
@@ -281,6 +285,11 @@ func validateCustomStepInput(stepTypeName string, schema *jsonschema.Resolved, i
281285 )
282286 }
283287 if err := schema .Validate (working ); err != nil {
288+ if runtimeInput , ok := customStepRuntimeValidationInput (schema .Schema (), working ); ok {
289+ if runtimeErr := schema .Validate (runtimeInput ); runtimeErr == nil {
290+ return working , nil
291+ }
292+ }
284293 return nil , core .NewValidationError (
285294 "config" ,
286295 input ,
@@ -290,6 +299,219 @@ func validateCustomStepInput(stepTypeName string, schema *jsonschema.Resolved, i
290299 return working , nil
291300}
292301
302+ func customStepRuntimeValidationInput (root * jsonschema.Schema , input map [string ]any ) (map [string ]any , bool ) {
303+ value , ok := customStepRuntimeValidationValue (root , root , input )
304+ if ! ok {
305+ return nil , false
306+ }
307+ typed , ok := value .(map [string ]any )
308+ return typed , ok
309+ }
310+
311+ func customStepRuntimeValidationValue (root , schema * jsonschema.Schema , value any ) (any , bool ) {
312+ schema = customStepRuntimeSchema (root , schema )
313+ if schema == nil {
314+ return nil , false
315+ }
316+
317+ switch typed := value .(type ) {
318+ case string :
319+ return customStepRuntimePlaceholder (schema , typed )
320+ case map [string ]any :
321+ return customStepRuntimeValidationObject (root , schema , typed )
322+ case []any :
323+ return customStepRuntimeValidationArray (root , schema , typed )
324+ default :
325+ return nil , false
326+ }
327+ }
328+
329+ func customStepRuntimeValidationObject (root , schema * jsonschema.Schema , value map [string ]any ) (map [string ]any , bool ) {
330+ var output map [string ]any
331+ for key , item := range value {
332+ propertySchema := customStepObjectPropertySchema (schema , key )
333+ next , ok := customStepRuntimeValidationValue (root , propertySchema , item )
334+ if ! ok {
335+ continue
336+ }
337+ if output == nil {
338+ output = make (map [string ]any , len (value ))
339+ maps .Copy (output , value )
340+ }
341+ output [key ] = next
342+ }
343+ return output , output != nil
344+ }
345+
346+ func customStepRuntimeValidationArray (root , schema * jsonschema.Schema , value []any ) ([]any , bool ) {
347+ var output []any
348+ for idx , item := range value {
349+ itemSchema := customStepArrayItemSchema (schema , idx )
350+ next , ok := customStepRuntimeValidationValue (root , itemSchema , item )
351+ if ! ok {
352+ continue
353+ }
354+ if output == nil {
355+ output = append ([]any (nil ), value ... )
356+ }
357+ output [idx ] = next
358+ }
359+ return output , output != nil
360+ }
361+
362+ func customStepObjectPropertySchema (schema * jsonschema.Schema , key string ) * jsonschema.Schema {
363+ if schema == nil {
364+ return nil
365+ }
366+ if propertySchema , ok := schema .Properties [key ]; ok {
367+ return propertySchema
368+ }
369+ return schema .AdditionalProperties
370+ }
371+
372+ func customStepArrayItemSchema (schema * jsonschema.Schema , idx int ) * jsonschema.Schema {
373+ if schema == nil {
374+ return nil
375+ }
376+ switch {
377+ case idx < len (schema .PrefixItems ):
378+ return schema .PrefixItems [idx ]
379+ case idx < len (schema .ItemsArray ):
380+ return schema .ItemsArray [idx ]
381+ case schema .Items != nil :
382+ return schema .Items
383+ default :
384+ return schema .AdditionalItems
385+ }
386+ }
387+
388+ func customStepRuntimePlaceholder (schema * jsonschema.Schema , value string ) (any , bool ) {
389+ if ! customStepRuntimeExpressionRegexp .MatchString (value ) {
390+ return nil , false
391+ }
392+
393+ schemaType , ok := schemaScalarType (schema )
394+ if ! ok && schema .Const != nil {
395+ schemaType , ok = inferScalarType (* schema .Const )
396+ }
397+ if ! ok {
398+ return nil , false
399+ }
400+
401+ wholeExpression := customStepWholeRuntimeExpressionRegexp .MatchString (value )
402+ if schemaType != core .ParamDefTypeString || len (schema .Enum ) > 0 || schema .Const != nil {
403+ if ! wholeExpression {
404+ return nil , false
405+ }
406+ }
407+
408+ return customStepPlaceholderForSchema (schema , schemaType )
409+ }
410+
411+ func customStepPlaceholderForSchema (schema * jsonschema.Schema , schemaType string ) (any , bool ) {
412+ if schema .Const != nil {
413+ return cloneAny (* schema .Const ), true
414+ }
415+ if len (schema .Enum ) > 0 {
416+ return cloneAny (schema .Enum [0 ]), true
417+ }
418+
419+ switch schemaType {
420+ case core .ParamDefTypeString :
421+ return customStepStringPlaceholder (schema ), true
422+ case core .ParamDefTypeInteger :
423+ return customStepIntegerPlaceholder (schema ), true
424+ case core .ParamDefTypeNumber :
425+ return customStepNumberPlaceholder (schema ), true
426+ case core .ParamDefTypeBoolean :
427+ return false , true
428+ default :
429+ return nil , false
430+ }
431+ }
432+
433+ func customStepStringPlaceholder (schema * jsonschema.Schema ) string {
434+ length := 1
435+ if schema .MaxLength != nil && * schema .MaxLength == 0 {
436+ length = 0
437+ }
438+ if schema .MinLength != nil && * schema .MinLength > length {
439+ length = * schema .MinLength
440+ }
441+ if schema .MaxLength != nil && length > * schema .MaxLength {
442+ length = * schema .MaxLength
443+ }
444+ return strings .Repeat ("x" , length )
445+ }
446+
447+ func customStepIntegerPlaceholder (schema * jsonschema.Schema ) int {
448+ value := 0
449+ if schema .Minimum != nil && float64 (value ) < * schema .Minimum {
450+ value = ceilInt (* schema .Minimum )
451+ }
452+ if schema .ExclusiveMinimum != nil && float64 (value ) <= * schema .ExclusiveMinimum {
453+ value = floorInt (* schema .ExclusiveMinimum ) + 1
454+ }
455+ if schema .Maximum != nil && float64 (value ) > * schema .Maximum {
456+ value = floorInt (* schema .Maximum )
457+ }
458+ if schema .ExclusiveMaximum != nil && float64 (value ) >= * schema .ExclusiveMaximum {
459+ value = ceilInt (* schema .ExclusiveMaximum ) - 1
460+ }
461+ return value
462+ }
463+
464+ func customStepNumberPlaceholder (schema * jsonschema.Schema ) float64 {
465+ value := 0.0
466+ if schema .Minimum != nil && value < * schema .Minimum {
467+ value = * schema .Minimum
468+ }
469+ if schema .ExclusiveMinimum != nil && value <= * schema .ExclusiveMinimum {
470+ value = * schema .ExclusiveMinimum + 1
471+ }
472+ if schema .Maximum != nil && value > * schema .Maximum {
473+ value = * schema .Maximum
474+ }
475+ if schema .ExclusiveMaximum != nil && value >= * schema .ExclusiveMaximum {
476+ value = * schema .ExclusiveMaximum - 1
477+ }
478+ return value
479+ }
480+
481+ func customStepRuntimeSchema (root , schema * jsonschema.Schema ) * jsonschema.Schema {
482+ if schema == nil || schema .Ref == "" {
483+ return schema
484+ }
485+ if name , ok := strings .CutPrefix (schema .Ref , "#/$defs/" ); ok && root != nil {
486+ return root .Defs [unescapeJSONPointerSegment (name )]
487+ }
488+ if name , ok := strings .CutPrefix (schema .Ref , "#/definitions/" ); ok && root != nil {
489+ return root .Definitions [unescapeJSONPointerSegment (name )]
490+ }
491+ return schema
492+ }
493+
494+ func unescapeJSONPointerSegment (segment string ) string {
495+ segment = strings .ReplaceAll (segment , "~1" , "/" )
496+ return strings .ReplaceAll (segment , "~0" , "~" )
497+ }
498+
499+ func ceilInt (value float64 ) int {
500+ result := int (value )
501+ if float64 (result ) < value {
502+ result ++
503+ }
504+ return result
505+ }
506+
507+ func floorInt (value float64 ) int {
508+ result := int (value )
509+ if float64 (result ) > value {
510+ result --
511+ }
512+ return result
513+ }
514+
293515func renderCustomStepTemplate (stepTypeName string , template map [string ]any , input map [string ]any ) (map [string ]any , error ) {
294516 rendered , err := renderCustomStepTemplateValue (stepTypeName , template , map [string ]any {"input" : input })
295517 if err != nil {
0 commit comments