$rule['max']))return[$value,'The value is outside the allowed range.'];} if(isset($rule['choices'])&&!in_array($value,$rule['choices'],true))return[$value,'Select an allowed value.']; if(is_string($value)&&(mb_strlen($value)>200||(isset($rule['pattern'])&&!preg_match('~^(?:'.$rule['pattern'].')$~D',$value))))return[$value,'The value has an invalid format.']; if(is_array($value)&&!array_is_list($value)){ $properties=$rule['properties']??[];$unknown=array_diff(array_keys($value),array_keys($properties));if($unknown){sort($unknown);return[$value,'Unknown keys: '.implode(', ',$unknown).'.'];} $result=is_array($rule['default']??null)?$rule['default']:[]; foreach($value as $key=>$item){if(!isset($properties[$key]))continue;[$normalized,$error]=self::value($item,$properties[$key]);if($error)return[$value,$key.': '.$error];$result[$key]=$normalized;} $missing=array_diff($rule['required']??[],array_keys($result));if($missing){sort($missing);return[$value,'Required keys: '.implode(', ',$missing).'.'];}$value=$result; } return[$value,null]; } public static function validate($spec): array { $schema=self::schema();if(!$schema)return['ok'=>false,'spec'=>$spec,'errors'=>['schema'=>'Parameter catalog is unavailable.']]; if(!is_array($spec)|| (array_is_list($spec)&&$spec!==[]))return['ok'=>false,'spec'=>$spec,'errors'=>['spec'=>'Expected an object.']]; $errors=[];$normalized=[]; foreach(array_diff(array_keys($spec),['model','run','parameters','surface','maps']) as $key)$errors[$key]='Unknown spec section.'; foreach(['model','run'] as $section){$values=array_key_exists($section,$spec)?$spec[$section]:[];if(!is_array($values)||(array_is_list($values)&&$values!==[])){$errors[$section]='Expected an object.';continue;} $normalized[$section]=$section==='model'?($schema['model_defaults']):$schema['run_defaults']; foreach($values as $key=>$value){if(!isset($schema[$section][$key])){$errors[$section.'.'.$key]='Unknown field.';continue;}[$result,$error]=self::value($value,$schema[$section][$key]);if($error)$errors[$section.'.'.$key]=$error;else $normalized[$section][$key]=$result;} } $parameters=array_key_exists('parameters',$spec)?$spec['parameters']:[];if($parameters===[])$parameters=[];$normalized['parameters']=[]; if(!is_array($parameters)||(array_is_list($parameters)&&$parameters!==[]))$errors['parameters']='Expected an object.'; else {$fields=[];foreach($schema['fields'] as $field)$fields[$field['name']]=$field; foreach($parameters as $name=>$value){$field=$fields[$name]??null;if(!$field){$errors['parameters.'.$name]='Unknown parameter.';continue;} if(empty($field['editable'])){if($value!==($field['default']??null))$errors['parameters.'.$name]='This input is managed by the application.';continue;} $rule=$field;if($name==='ozone')$rule['types']=['boolean','object']; if($name==='vegetation'&&($value===true||$value===false)){$normalized['parameters'][$name]=$value;continue;} [$result,$error]=self::value($value,$rule);if($error)$errors['parameters.'.$name]=$error;else $normalized['parameters'][$name]=$result; } } if(array_key_exists('maps',$spec)){$maps=$spec['maps'];$rules=$schema['maps']['fields']??[];if(!is_array($maps)||(array_is_list($maps)&&$maps!==[]))$errors['maps']='Expected a map version object.';else{$normalized['maps']=[];foreach($maps as $field=>$id){if(!isset($rules[$field])){$errors['maps.'.$field]='Unsupported map field.';continue;}if(!is_string($id)||!preg_match('/^[a-f0-9]{32}$/',$id))$errors['maps.'.$field]='Expected a saved map version ID.';else $normalized['maps'][$field]=$id;}}} if(array_key_exists('surface',$spec)){$surface=$spec['surface'];$rules=$schema['surface']??[];if(!is_array($surface)||(array_is_list($surface)&&$surface!==[]))$errors['surface']='Expected an object.';else{$normalized['surface']=[];foreach(array_diff(array_keys($surface),array_keys($rules)) as $key)$errors['surface.'.$key]='Unknown field.';foreach($rules as $key=>$rule){$item=array_key_exists($key,$surface)?$surface[$key]:($rule['default']??null);[$result,$error]=self::value($item,$rule);if($error)$errors['surface.'.$key]=$error;else $normalized['surface'][$key]=$result;}}} return['ok'=>!$errors,'spec'=>$normalized,'errors'=>$errors]; } }