From 5d84741a6fa59233312673bd3e674e1e63bb5565 Mon Sep 17 00:00:00 2001 From: bruno_p_reis Date: Fri, 19 Dec 2008 19:24:26 +0000 Subject: [PATCH] adding the php type cast mode, where string data that represents a valid double or integer is validated ok agains a schema definition of number or integer. git-svn-id: https://jsonschemaphpv.svn.sourceforge.net/svnroot/jsonschemaphpv/trunk@4 14558f9d-7ea9-46ec-92da-52a2cad6a683 --- JsSchema.php | 410 -------------------- JsonSchema.php | 71 +++- interface.js | 16 +- seleniumTests/SUITEPhpJsJsonSchemaValidator | 2 +- seleniumTests/phpTypeCastMode | 137 +++++++ validate.php | 3 + validator.html | 3 + 7 files changed, 220 insertions(+), 422 deletions(-) delete mode 100644 JsSchema.php create mode 100644 seleniumTests/phpTypeCastMode diff --git a/JsSchema.php b/JsSchema.php deleted file mode 100644 index 82a7a3c..0000000 --- a/JsSchema.php +++ /dev/null @@ -1,410 +0,0 @@ -$propName)) { - self::checkProp($instance,$instance->$propName,'','',$_changing); - } - // show results - $obj = new stdClass(); - $obj->valid = ! ((boolean)count(self::$errors)); - $obj->errors = self::$errors; - return $obj; - } - - static function incrementPath($path,$i) { - if($path) { - if(is_int($i)) { - $path .= '['.$i.']'; - } - elseif($i = '') { - $path .= ''; - } - else { - $path .= '.'.$i; - } - } - else { - $path = $i; - } - return $path; - } - - static function checkArray($value,$schema,$path,$i,$_changing) { - //verify items - if(isset($schema->items)) { - //tuple typing - if(is_array($schema->items)) { - foreach($value as $k=>$v) { - if(array_key_exists($k,$schema->items)) { - self::checkProp($v,$schema->items[$k],$path,$k,$_changing); - } - else { - // aditional array properties - if(array_key_exists('additionalProperties',$schema)) { - if($schema->additionalProperties === false) { - self::adderror( - $path, - 'The item '.$i.'['.$k.'] is not defined in the objTypeDef and the objTypeDef does not allow additional properties' - ); - } - else { - self::checkProp($v,$schema->additionalProperties,$path,$k,$_changing); - } - } - } - }//foreach($value as $k=>$v) { - // treat when we have more schema definitions than values - for($k = count($value); $k < count($schema->items); $k++) { - self::checkProp( - new JsSchemaUndefined(), - $schema->items[$k], $path, $k, $_changing - ); - } - } - // just one type definition for the whole array - else { - foreach($value as $k=>$v) { - self::checkProp($v,$schema->items,$path,$k,$_changing); - } - } - } - // verify number of array items - if(isset($schema->minItems) && count($value) < $schema->minItems) { - self::adderror($path,"There must be a minimum of " . $schema->minItems . " in the array"); - } - if(isset($schema->maxItems) && count($value) > $schema->maxItems) { - self::adderror($path,"There must be a maximum of " . $schema->maxItems . " in the array"); - } - } - - static function checkProp($value, $schema, $path, $i = '', $_changing = false) { - if (!is_object($schema)) { - return; - } - $path = self::incrementPath($path,$i); - // verify readonly - if($_changing && $schema.readonly) { - self::adderror($path,'is a readonly field, it can not be changed'); - } - // I think a schema cant be an array, only the items property - /*if(is_array($schema)) { - if(!is_array($value)) { - return array(array('property'=>$path,'message'=>'An array tuple is required')); - } - for($a = 0; $a < count($schema); $a++) { - self::$errors = array_merge( - self::$errors, - self::checkProp($value->$a,$schema->$a,$path,$i,$_changing) - ); - return self::$errors; - } - }*/ - // if it extends another schema, it must pass that schema as well - if(isset($schema->extends)) { - self::checkProp($value,$schema->extends,$path,$i,$_changing); - } - // verify optional values - if (is_object($value) && $value instanceOf JsSchemaUndefined) { - if ( isset($schema->optional) ? !$schema->optional : true) { - self::adderror($path,"is missing and it is not optional"); - } - } - // normal verifications - else { - self::$errors = array_merge( - self::$errors, - self::checkType( isset($schema->type) ? $schema->type : null , $value, $path) - ); - } - if(array_key_exists('disallow',$schema)) { - $errorsBeforeDisallowCheck = self::$errors; - Dbg::object($errorsBeforeDisallowCheck,'$errorsBeforeDisallowCheck'); - $response = self::checkType($schema->disallow, $value, $path); - Dbg::object(count(self::$errors),'after errors'); - if( - ( count($errorsBeforeDisallowCheck) == count(self::$errors) ) && - !count($response) - ) { - self::adderror($path," disallowed value was matched"); - } - else { - self::$errors = $errorsBeforeDisallowCheck; - } - } - //verify the itens on an array and min and max number of items. - if(is_array($value)) { - self::checkArray($value,$schema,$path,$i,$_changing); - } - ############ verificar! - elseif(isset($schema->properties) && is_object($value)) { - self::checkObj( - $value, - $schema->properties, - $path, - isset($schema->additionalProperties) ? $schema->additionalProperties : null, - $_changing - ); - } - // verify a regex pattern - if( isset($schema->pattern) && is_string($value) && !preg_match('/'.$schema->pattern.'/',$value)) { - self::adderror($path,"does not match the regex pattern " . $schema->pattern); - } - // verify maxLength, minLength, maximum and minimum values - if( isset($schema->maxLength) && is_string($value) && (strlen($value) > $schema->maxLength)) { - self::adderror($path,"may be at most" . $schema->maxLength . " characters long"); - } - if( isset($schema->minLength) && is_string($value) && strlen($value) < $schema->minLength) { - self::adderror($path,"must be at least " . $schema->minLength . " characters long"); - } - if( isset($schema->minimum) && gettype($value) == gettype($schema->minimum) && $value < $schema->minimum) { - self::adderror($path,"must have a minimum value of " . $schema->minimum); - } - if( isset($schema->maximum) && gettype($value) == gettype($schema->maximum) && $value > $schema->maximum) { - self::adderror($path,"must have a maximum value of " . $schema->maximum); - } - // verify enum values - if(isset($schema->enum)) { - $found = false; - foreach($schema->enum as $possibleValue) { - if($possibleValue == $value) { - $found = true; - break; - } - } - if(!$found) { - self::adderror($path,"does not have a value in the enumeration " . implode(', ',$schema->enum)); - } - } - if( - isset($schema->maxDecimal) && - ( ($value * pow(10,$schema->maxDecimal)) != (int)($value * pow(10,$schema->maxDecimal)) ) - ) { - self::adderror($path,"may only have " . $schema->maxDecimal . " digits of decimal places"); - } - } - - static function adderror($path,$message) { - self::$errors[] = array( - 'property'=>$path, - 'message'=>$message - ); - } - - /** - * @return array - */ - static function checkType($type, $value, $path) { - if($type) { - $wrongType = false; - if(is_string($type) && $type !== 'any') { - if($type == 'null') { - if (!is_null($value)) { - $wrongType = true; - } - } - else { - if($type == 'number') { - if(!in_array(gettype($value),array('integer','double'))) { - $wrongType = true; - } - } elseif( $type !== gettype($value) ) { - $wrongType = true; - } - } - } - if($wrongType) { - return array( - array( - 'property'=>$path, - 'message'=>gettype($value)." value found, but a ".$type." is required " - ) - ); - } - // Union Types :: for now, just return the message for the last expected type!! - if(is_array($type)) { - $validatedOneType = false; - $errors = array(); - foreach($type as $tp) { - $error = self::checkType($tp,$value,$path); - if(!count($error)) { - $validatedOneType = true; - break; - } - else { - $errors[] = $error; - $errors = $error; - } - } - if(!$validatedOneType) { - return $errors; - } - } - elseif(is_object($type)) { - self::checkProp($value,$type,$path); - } - } - return array(); - } - - static function checkObj($instance, $objTypeDef, $path, $additionalProp,$_changing) { - if($objTypeDef instanceOf StdClass) { - if( ! ($instance instanceOf StdClass) || is_array($instance) ) { - self::$errors[] = array( - 'property'=>$path, - 'message'=>"an object is required" - ); - } - foreach($objTypeDef as $i=>$value) { - $value = - array_key_exists($i,$instance) ? - $instance->$i : - new JsSchemaUndefined(); - $propDef = $objTypeDef->$i; - self::checkProp($value,$propDef,$path,$i,$_changing); - } - } - // additional properties and requires - foreach($instance as $i=>$value) { - // verify additional properties, when its not allowed - if( !isset($objTypeDef->$i) && ($additionalProp === false) && $i !== '$schema' ) { - self::$errors[] = array( - 'property'=>$path, - 'message'=>"The property " . $i . " is not defined in the objTypeDef and the objTypeDef does not allow additional properties" - ); - } - // verify requires - if($objTypeDef && isset($objTypeDef->$i) && isset($objTypeDef->$i->requires)) { - $requires = $objTypeDef->$i->requires; - if(!array_key_exists($requires,$instance)) { - self::$errors[] = array( - 'property'=>$path, - 'message'=>"the presence of the property " . $i . " requires that " . $requires . " also be present" - ); - } - } - $value = $instance->$i; - - // To verify additional properties types. - if ($objTypeDef && is_object($objTypeDef) && !isset($objTypeDef->$i)) { - self::checkProp($value,$additionalProp,$path,$i); - } - // Verify inner schema definitions - $schemaPropName = '$schema'; - if (!$_changing && $value && isset($value->$schemaPropName)) { - self::$errors = array_merge( - self::$errors, - checkProp($value,$value->$schemaPropname,$path,$i) - ); - } - } - return self::$errors; - } -} - -class Dbg { - - static public $quietMode = false; - - static function func($print = true, $numStackSteps = 1) { - $ar = debug_backtrace(); - $ret = ''; - - for($a = $numStackSteps; $a >= 1; $a--) { - $line = $ar[$a-1]['line']; - $step = $ar[$a]; - $ret .= str_repeat('   ',$a).self::showStep($step,$print,$line); - } - if($print && !self::$quietMode) echo $ret; - return $ret; - } - - static function mark($title,$print = true) { - $ar = debug_backtrace(); - $ret = ''; - $line = $ar[0]['line']; - $ret = "[MARK]".$title.'(line '.$line.')
'; - if($print && !self::$quietMode) echo $ret; - return $ret; - } - - static function object($object,$linkTitle,$varDump = false,$print = true) { - static $divCount = 0; - $divCount++; - $ar = debug_backtrace(); - $ret = ''; - $line = $ar[0]['line']; - $ret = '[OBJECT]'.$linkTitle.''; - $ret .= '(line '.$line.')
'; - $ret .= ''; - if($print && !self::$quietMode) echo $ret; - return $ret; - } - - static function showStep($step,$print,$line) { - static $divCount = 0; - $ret = '[STEP]'.$step['class'] . $step['type'] . $step['function']; - if(count($step['args'])) { - $ret .= '('; - $comma = ''; - $exp = array(); - foreach($step['args'] as $num=>$arg) { - $divCount++; - if(in_array(gettype($arg),array('object','array'))) { - if(is_object($arg)) { - $type = get_class($arg); - } - else { - $type = gettype($arg); - } - $argVal = 'click to see'; - $exp[] = - ''; - } - else { - $type = gettype($arg); - if($type == 'string') { - $argVal = "'".$arg."'"; - } - else { - $argVal = $arg; - } - $argVal = ''.$argVal.''; - } - $ret .= $comma.' ' . $type . " " . $argVal; - $comma = ','; - } - $ret .= ') (line '.$line.')
'; - foreach($exp as $text) { - $ret .= '
' . $text . '
'; - } - } - return $ret; - } -} -?> \ No newline at end of file diff --git a/JsonSchema.php b/JsonSchema.php index 20314a3..80c8d00 100644 --- a/JsonSchema.php +++ b/JsonSchema.php @@ -1,6 +1,6 @@ disallow, $value, $path); - Dbg::object(count(self::$errors),'after errors'); if( ( count($errorsBeforeDisallowCheck) == count(self::$errors) ) && !count($response) @@ -210,7 +222,12 @@ class JsonSchema { if( isset($schema->minLength) && is_string($value) && strlen($value) < $schema->minLength) { self::adderror($path,"must be at least " . $schema->minLength . " characters long"); } - if( isset($schema->minimum) && gettype($value) == gettype($schema->minimum) && $value < $schema->minimum) { + + if( + isset($schema->minimum) && + gettype($value) == gettype($schema->minimum) && + $value < $schema->minimum + ) { self::adderror($path,"must have a minimum value of " . $schema->minimum); } if( isset($schema->maximum) && gettype($value) == gettype($schema->maximum) && $value > $schema->maximum) { @@ -251,9 +268,10 @@ class JsonSchema { } /** + * Take Care: Value is being passed by ref to continue validation with proper format. * @return array */ - static function checkType($type, $value, $path) { + static function checkType($type, &$value, $path) { if($type) { $wrongType = false; if(is_string($type) && $type !== 'any') { @@ -264,11 +282,21 @@ class JsonSchema { } else { if($type == 'number') { - if(!in_array(gettype($value),array('integer','double'))) { + if(self::$checkMode == self::CHECK_MODE_TYPE_CAST) { + $wrongType = !self::checkTypeCast($type,$value); + } + elseif(!in_array(gettype($value),array('integer','double'))) { $wrongType = true; } - } elseif( $type !== gettype($value) ) { - $wrongType = true; + } else{ + if( + self::$checkMode == self::CHECK_MODE_TYPE_CAST + && $type == 'integer' + ) { + $wrongType = !self::checkTypeCast($type,$value); + } elseif($type !== gettype($value)) { + $wrongType = true; + } } } } @@ -306,6 +334,31 @@ class JsonSchema { return array(); } + /** + * Take Care: Value is being passed by ref to continue validation with proper format. + */ + static function checkTypeCast($type,&$value) { + switch($type) { + case 'integer': + $castValue = (integer)$value; + break; + case 'number': + $castValue = (double)$value; + break; + default: + trigger_error('this method should only be called for the above supported types.'); + break; + } + if( (string)$value == (string)$castValue ) { + $res = true; + $value = $castValue; + } + else { + $res = false; + } + return $res; + } + static function checkObj($instance, $objTypeDef, $path, $additionalProp,$_changing) { if($objTypeDef instanceOf StdClass) { if( ! ($instance instanceOf StdClass) || is_array($instance) ) { diff --git a/interface.js b/interface.js index 8fe63aa..3d84f65 100644 --- a/interface.js +++ b/interface.js @@ -1,17 +1,29 @@ $(document).ready(function(){ $("#bt-validate-js").click(validateJs); $("#bt-validate-php").click(validatePhp); + $("#bt-validate-php-type-cast-mode").click(validatePhpTypeCastMode); }); -function validatePhp() { +function validatePhpTypeCastMode() { + validatePhp(true); +} + +function validatePhp(typeCastMode) { + if(typeCastMode == true) { + typeCastMode = true; + } + else { + typeCastMode = false; + } + $('#resultados').html('. . . w o r k i n g . . . '); schema = $('#schema').val(); json = $('#json').val(); $.getJSON( "validate.php", - {"schema":schema,"json":json}, + {"schema":schema,"json":json,"typeCastMode":typeCastMode}, phpCallback ); } diff --git a/seleniumTests/SUITEPhpJsJsonSchemaValidator b/seleniumTests/SUITEPhpJsJsonSchemaValidator index 21b4d39..5c9bc01 100644 --- a/seleniumTests/SUITEPhpJsJsonSchemaValidator +++ b/seleniumTests/SUITEPhpJsJsonSchemaValidator @@ -3,7 +3,6 @@ - Test Suite @@ -26,6 +25,7 @@ maxDecimal selfDefinedSchema extends +phpTypeCastMode disallow wrongMessagesFailingTestCase diff --git a/seleniumTests/phpTypeCastMode b/seleniumTests/phpTypeCastMode new file mode 100644 index 0000000..b96177d --- /dev/null +++ b/seleniumTests/phpTypeCastMode @@ -0,0 +1,137 @@ + + + + + + +phpTypeCastMode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
phpTypeCastMode
open/validator.html
typeschema{
  "type":"object",
  "properties":{
    "a":{"type":"number"}
  }
}
typejson{
  "a":"c"
}
clickbt-validate-php
waitForTextPresentJSON:
verifyTextPresentJSON: INVALID
clickbt-validate-php-type-cast-mode
waitForTextPresentJSON:
verifyTextPresentJSON: INVALID
typejson{
  "a":"9"
}
clickbt-validate-php
waitForTextPresentJSON:
verifyTextPresentJSON: INVALID
clickbt-validate-php-type-cast-mode
waitForTextPresentJSON:
verifyTextPresentJSON: VALID
typeschema{
  "type":"object",
  "properties":{
    "a":{"type":"integer","maximum":8}
  }
}
clickbt-validate-php-type-cast-mode
waitForTextPresentJSON:
verifyTextPresentmust have a maximum value of 8
typeschema{
  "type":"object",
  "properties":{
    "a":{"type":"integer","maximum":8.0}
  }
}
clickbt-validate-php-type-cast-mode
waitForTextPresentJSON:
verifyTextPresentJSON: VALID
+ + diff --git a/validate.php b/validate.php index 2ba7106..899f9ae 100644 --- a/validate.php +++ b/validate.php @@ -18,6 +18,9 @@ if(!$json) { trigger_error('Could not parse the JSON object.',E_USER_ERROR); } +if($_REQUEST['typeCastMode'] == 'true') { + JsonSchema::$checkMode = JsonSchema::CHECK_MODE_TYPE_CAST; +} $result = JsonSchema::validate( $json, $schema diff --git a/validator.html b/validator.html index fc00226..971f6ce 100644 --- a/validator.html +++ b/validator.html @@ -41,6 +41,9 @@ +
...