Skip to content

Commit

Permalink
ensure that custom schemas conform to default schemas + return Custom…
Browse files Browse the repository at this point in the history
… objects
  • Loading branch information
OriHoch committed May 11, 2017
1 parent 8dc17e1 commit ec82007
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 58 deletions.
7 changes: 7 additions & 0 deletions src/Datapackages/CustomDatapackage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
namespace frictionlessdata\datapackage\Datapackages;

class CustomDatapackage extends DefaultDatapackage
{

}
7 changes: 7 additions & 0 deletions src/Datapackages/TabularDatapackage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
namespace frictionlessdata\datapackage\Datapackages;

class TabularDatapackage extends DefaultDatapackage
{

}
2 changes: 1 addition & 1 deletion src/Exceptions/DatapackageValidationFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ class DatapackageValidationFailedException extends \Exception
public function __construct($validationErrors)
{
$this->validationErrors = $validationErrors;
parent::__construct("DefaultDatapackage validation failed: ".DatapackageValidationError::getErrorMessages($validationErrors));
parent::__construct("Datapackage validation failed: ".DatapackageValidationError::getErrorMessages($validationErrors));
}
}
26 changes: 23 additions & 3 deletions src/Factory.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php namespace frictionlessdata\datapackage;
use frictionlessdata\datapackage\Datapackages\BaseDatapackage;
use frictionlessdata\datapackage\Resources\BaseResource;
use frictionlessdata\datapackage\Resources\DefaultResource;
use JsonSchema\Validator;

/**
Expand All @@ -21,7 +23,7 @@ class Factory
* - URL (must be in either 'http' or 'https' schemes)
* - local filesystem (POSIX) path
* @param mixed $source
* @param null|string $basePath
* @param null|string $basePath optional, required only if you want to use relative paths
* @return Datapackages\BaseDatapackage
* @throws Exceptions\DatapackageInvalidSourceException
* @throws Exceptions\DatapackageValidationFailedException
Expand Down Expand Up @@ -131,7 +133,16 @@ public static function validate($source, $basePath=null)

public static function getDatapackageClass($descriptor)
{
return Registry::getDatapackageClass($descriptor);
$profile = Registry::getDatapackageValidationProfile($descriptor);
if ($profile == "tabular-data-package") {
$datapackageClass = "frictionlessdata\\datapackage\\Datapackages\TabularDatapackage";
} elseif ($profile == "data-package") {
$datapackageClass = "frictionlessdata\\datapackage\\Datapackages\DefaultDatapackage";
} else {
$datapackageClass = "frictionlessdata\\datapackage\\Datapackages\CustomDatapackage";
}
/** @var BaseDatapackage $datapackageClass */
return $datapackageClass;
}

/**
Expand All @@ -140,7 +151,16 @@ public static function getDatapackageClass($descriptor)
*/
public static function getResourceClass($descriptor)
{
return Registry::getResourceClass($descriptor);
$profile = Registry::getResourceValidationProfile($descriptor);
if ($profile == "tabular-data-resource") {
$resourceClass = "frictionlessdata\\datapackage\\Resources\TabularResource";
} elseif ($profile == "data-resource") {
$resourceClass = "frictionlessdata\\datapackage\\Resources\DefaultResource";
} else {
$resourceClass = "frictionlessdata\\datapackage\\Resources\CustomResource";
}
/** @var BaseResource $resourceClass */
return $resourceClass;
}
/**
* allows extending classes to add custom sources
Expand Down
57 changes: 20 additions & 37 deletions src/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,13 @@
use frictionlessdata\datapackage\Resources\BaseResource;

/**
* repository of known profiles and the corresponding classes and validation requirements
* repository of known profiles and the corresponding schemas
*/
class Registry
{
/**
* @param $descriptor
* @return BaseResource::class
* get the profile which should be used for validation from the given resource descriptor
*/
public static function getResourceClass($descriptor)
{
if (static::getResourceValidationProfile($descriptor) == "tabular-data-resource") {
$resourceClass = "frictionlessdata\\datapackage\\Resources\\TabularResource";
} else {
$resourceClass = "frictionlessdata\\datapackage\\Resources\\DefaultResource";
}
/** @var BaseResource $resourceClass */
return $resourceClass;
}

public static function getResourceValidationProfile($descriptor)
{
if (isset($descriptor->profile) && $descriptor->profile != "default") {
Expand All @@ -30,11 +18,10 @@ public static function getResourceValidationProfile($descriptor)
}
}

public static function getDatapackageClass($descriptor)
{
return "frictionlessdata\\datapackage\\Datapackages\\DefaultDatapackage";
}

/**
* get the profile which should be used for validation from the given datapackage descriptor
* corresponds to the id from the registry
*/
public static function getDatapackageValidationProfile($descriptor)
{
if (isset($descriptor->profile) && $descriptor->profile != "default") {
Expand All @@ -44,28 +31,24 @@ public static function getDatapackageValidationProfile($descriptor)
}
}

public static function getDatapackageJsonSchemaFile($profile)
{
if (in_array($profile, ["data-package", "tabular-data-package"])) {
// known profile id
return realpath(dirname(__FILE__))."/Validators/schemas/".$profile.".json";
} else {
// unknown profile / file or url
return false;
}
}

public static function getResourceJsonSchemaFile($profile)
/**
* given a normalized profile - get the corresponding schema file for known schema in the registry
* returns false in case of unknown schema
* works the same for both datapackage schema and resource schemas
*/
public static function getJsonSchemaFile($profile)
{
if (in_array($profile, ["data-resource", "tabular-data-resource"])) {
// known profile id
return realpath(dirname(__FILE__))."/Validators/schemas/".$profile.".json";
} else {
// unknown profile / file or url
return false;
foreach (static::getAllSchemas() as $schema) {
if ($schema->id != "registry" && $schema->id == $profile) {
return realpath(dirname(__FILE__))."/Validators/schemas/".$schema->schema_path;
}
}
return false;
}

/**
* returns array of all known schemas in the registry
*/
public static function getAllSchemas()
{
// registry schema
Expand Down
6 changes: 6 additions & 0 deletions src/Resources/CustomResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace frictionlessdata\datapackage\Resources;

class CustomResource extends DefaultResource
{
}
12 changes: 6 additions & 6 deletions src/Validators/BaseValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ protected function getValidationErrorMessage($error)
* does the validation, adds errors to the validator object using _addError method
*/
protected function validateSchema()
{
$this->validateSchemaUrl($this->getValidationSchemaUrl());
}

protected function validateSchemaUrl($url)
{
$validator = new \JsonSchema\Validator();
$descriptor = $this->getDescriptorForValidation();
$validator->validate(
$descriptor,
(object)[
"\$ref" => $this->getValidationSchemaUrl()
]
);
$validator->validate($descriptor, (object)["\$ref" => $url]);
if (!$validator->isValid()) {
foreach ($validator->getErrors() as $error) {
$this->addError(
Expand Down
15 changes: 14 additions & 1 deletion src/Validators/DatapackageValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ protected function getValidationProfile()
return Registry::getDatapackageValidationProfile($this->descriptor);
}

protected function validateSchema()
{
parent::validateSchema();
if ($this->getValidationProfile() != "data-package") {
// all schemas must be an extension of datapackage spec
$this->validateSchemaUrl(
$this->convertValidationSchemaFilenameToUrl(
$this->getJsonSchemaFileFromRegistry("data-package")
)
);
}
}

protected function validateKeys()
{
foreach ($this->descriptor->resources as $resourceDescriptor) {
Expand All @@ -35,7 +48,7 @@ protected function resourceValidate($resourceDescriptor)

protected function getJsonSchemaFileFromRegistry($profile)
{
if ($filename = Registry::getDatapackageJsonSchemaFile($profile)) {
if ($filename = Registry::getJsonSchemaFile($profile)) {
return $filename;
} else {
return parent::getJsonSchemaFileFromRegistry($profile);
Expand Down
2 changes: 1 addition & 1 deletion src/Validators/ResourceValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected function validateKeys()

protected function getJsonSchemaFileFromRegistry($profile)
{
if ($filename = Registry::getResourceJsonSchemaFile($profile)) {
if ($filename = Registry::getJsonSchemaFile($profile)) {
return $filename;
} else {
return parent::getJsonSchemaFileFromRegistry($profile);
Expand Down
6 changes: 3 additions & 3 deletions tests/DatapackageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function testNativePHPObjectWithoutBasePathShouldFail()
$descriptor = $this->simpleDescriptor;
$this->assertDatapackageException(
"frictionlessdata\\datapackage\\Exceptions\\DatapackageValidationFailedException",
'DefaultDatapackage validation failed: data source file does not exist or is not readable: foo.txt',
'Datapackage validation failed: data source file does not exist or is not readable: foo.txt',
function() use ($descriptor) { Factory::datapackage($descriptor); }
);
}
Expand All @@ -68,7 +68,7 @@ public function testJsonStringWithoutBasePathShouldFail()
$source = json_encode($this->simpleDescriptor);
$this->assertDatapackageException(
"frictionlessdata\\datapackage\\Exceptions\\DatapackageValidationFailedException",
'DefaultDatapackage validation failed: data source file does not exist or is not readable: foo.txt',
'Datapackage validation failed: data source file does not exist or is not readable: foo.txt',
function() use ($source) { Factory::datapackage($source); }
);
}
Expand Down Expand Up @@ -186,7 +186,7 @@ public function testDatapackageValidationFailedShouldPreventConstruct()
} catch (Exceptions\DatapackageValidationFailedException $e) {
$caughtException = $e;
}
$this->assertEquals("DefaultDatapackage validation failed: [resources] The property resources is required", $caughtException->getMessage());
$this->assertEquals("Datapackage validation failed: [resources] The property resources is required", $caughtException->getMessage());
}

public function testTabularResourceDescriptorValidation()
Expand Down
29 changes: 23 additions & 6 deletions tests/RegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function testDatapackageWithDataPackageProfile()
public function testDatapackageWithTabularDataPackageProfile()
{
$this->assertDatapackageClassProfile(
"frictionlessdata\\datapackage\\Datapackages\\DefaultDatapackage",
"frictionlessdata\\datapackage\\Datapackages\\TabularDatapackage",
"tabular-data-package",
(object)["profile" => "tabular-data-package"]
);
Expand Down Expand Up @@ -99,7 +99,7 @@ public function testCustomProfileFromJsonSchemaFile()
$this->fail();
} catch (DatapackageValidationFailedException $e) {
$this->assertEquals(
"DefaultDatapackage validation failed: [custom] The property custom is required",
"Datapackage validation failed: [custom] The property custom is required",
$e->getMessage()
);
}
Expand All @@ -109,7 +109,7 @@ public function testCustomProfileFromJsonSchemaFile()
$this->fail();
} catch (DatapackageValidationFailedException $e) {
$this->assertEquals(
"DefaultDatapackage validation failed: [custom[0]] Integer value found, but a string is required, [custom[1]] Integer value found, but a string is required, [custom[2]] Integer value found, but a string is required",
"Datapackage validation failed: [custom[0]] Integer value found, but a string is required, [custom[1]] Integer value found, but a string is required, [custom[2]] Integer value found, but a string is required",
$e->getMessage()
);
}
Expand All @@ -120,7 +120,7 @@ public function testCustomProfileFromJsonSchemaFile()
$this->fail("should raise an exception because test-custom-profile requires foobar attribute (array of strings)");
} catch (DatapackageValidationFailedException $e) {
$this->assertEquals(
"DefaultDatapackage validation failed: [foobar] String value found, but an array is required",
"Datapackage validation failed: [foobar] String value found, but an array is required",
$e->getMessage()
);
}
Expand All @@ -141,15 +141,32 @@ public function testCustomProfileFromJsonSchemaFile()
], $datapackage->descriptor());
}

public function testCustomSchemaMustConformToDatapackageSchema()
{
$descriptor = (object)[
"profile" => "http://json-schema.org/schema",
"resources" => [] // this is allows for json-schema.org/schema - but for datapackage it has minimum of 1
];
try {
Factory::datapackage($descriptor);
$this->fail();
} catch (DatapackageValidationFailedException $e) {
$this->assertEquals(
'Datapackage validation failed: [resources] There must be a minimum of 1 items in the array',
$e->getMessage()
);
}
}

protected function assertDatapackageClassProfile($expectedClass, $expectedProfile, $descriptor)
{
$this->assertEquals($expectedClass, Registry::getDatapackageClass($descriptor));
$this->assertEquals($expectedClass, Factory::getDatapackageClass($descriptor));
$this->assertEquals($expectedProfile, Registry::getDatapackageValidationProfile($descriptor));
}

protected function assertResourceClassProfile($expectedClass, $expectedProfile, $descriptor)
{
$this->assertEquals($expectedClass, Registry::getResourceClass($descriptor));
$this->assertEquals($expectedClass, Factory::getResourceClass($descriptor));
$this->assertEquals($expectedProfile, Registry::getResourceValidationProfile($descriptor));
}

Expand Down

0 comments on commit ec82007

Please sign in to comment.