Skip to content

Commit

Permalink
Add basic validation support (#5)
Browse files Browse the repository at this point in the history
* coding style fixes, minor refactoring
* added DatapackageValidator (depends on SchemaValidator from frictionlessdata/tableschema)
* added basic datapackage validation functionality
  • Loading branch information
OriHoch authored Apr 18, 2017
1 parent 9929166 commit 34593be
Show file tree
Hide file tree
Showing 18 changed files with 949 additions and 205 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The project follows the [Open Knowledge International coding standards](https://

All PHP Code should conform to [PHP-FIG](http://www.php-fig.org/psr/) accepted PSRs.

Flow Framework has a nice guide regarding coding standards:
* [Printable summary of most important coding guidelines on one page **(.pdf)**](http://flowframework.readthedocs.io/en/stable/_downloads/Flow_Coding_Guidelines_on_one_page.pdf)
* [The full guide **(.html)**](http://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartV/CodingGuideLines/PHP.html)


## Getting Started

Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ $ composer require frictionlessdata/datapackage
```php
use frictionlessdata\datapackage;

$datapackage = new Datapackage("tests/fixtures/multi_data_datapackage.json");
// iterate over the data
$datapackage = new datapackage\Datapackage("tests/fixtures/multi_data_datapackage.json");
foreach ($datapackage as $resource) {
print("-- ".$resource->name()." --");
$i = 0;
Expand All @@ -34,6 +35,14 @@ foreach ($datapackage as $resource) {
}
}
}

// validate the descriptor
$validationErrors = datapackage\Datapackage::validate("tests/fixtures/simple_invalid_datapackage.json");
if (count($validationErrors) == 0) {
print("descriptor is valid");
} else {
print(datapackage\DatapackageValidationError::getErrorMessages($validationErrors));
}
```


Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"description": "A utility library for working with Data Packages",
"license": "MIT",
"require": {
"php": ">=5.4"
"php": ">=5.4",
"justinrainbow/json-schema": "^5.2",
"frictionlessdata/tableschema": "^0.1.2"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35",
Expand All @@ -15,6 +17,6 @@
}
},
"scripts": {
"test": "phpunit --debug tests/ --coverage-clover coverage-clover.xml"
"test": "phpunit --debug --coverage-clover coverage-clover.xml --bootstrap tests/autoload.php tests/"
}
}
31 changes: 14 additions & 17 deletions src/DataStream.php
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
<?php namespace frictionlessdata\datapackage;

<?php
namespace frictionlessdata\datapackage;

class DataStream implements \Iterator
{
protected $_currentLineNumber = 0;
protected $_fopenResource;
protected $_dataSource;

public function __construct($dataSource)
{
try {
$this->_fopenResource = fopen($dataSource, "r");
$this->fopenResource = fopen($dataSource, "r");
} catch (\Exception $e) {
throw new DataStreamOpenException("Failed to open source ".json_encode($dataSource));
throw new Exceptions\DataStreamOpenException("Failed to open source ".json_encode($dataSource).": ".json_encode($e->getMessage()));
}
}

public function __destruct()
{
fclose($this->_fopenResource);
fclose($this->fopenResource);
}

public function rewind() {
if ($this->_currentLineNumber != 0) {
if ($this->currentLineNumber != 0) {
throw new \Exception("DataStream does not support rewind, sorry");
}
}

public function current() {
$line = fgets($this->_fopenResource);
$line = fgets($this->fopenResource);
if ($line === false) {
return "";
} else {
Expand All @@ -37,17 +33,18 @@ public function current() {
}

public function key() {
return $this->_currentLineNumber;
return $this->currentLineNumber;
}

public function next() {
$this->_currentLineNumber++;
$this->currentLineNumber++;
}

public function valid() {
return (!feof($this->_fopenResource));
return (!feof($this->fopenResource));
}
}


class DataStreamOpenException extends \Exception {};
protected $currentLineNumber = 0;
protected $fopenResource;
protected $dataSource;
}
119 changes: 80 additions & 39 deletions src/Datapackage.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php namespace frictionlessdata\datapackage;

<?php
namespace frictionlessdata\datapackage;

/**
* Datapackage representation, supports loading from the following sources:
Expand All @@ -10,80 +10,121 @@
*/
class Datapackage implements \Iterator
{
protected $_descriptor;
protected $_currentResourcePosition = 0;
protected $_basePath;

public function __construct($source, $basePath=null)
{
if (is_object($source)) {
$this->_descriptor = $source;
$this->_basePath = $basePath;
$this->descriptor = $source;
$this->basePath = $basePath;
} elseif (is_string($source)) {
if (Utils::is_json_string($source)) {
if (Utils::isJsonString($source)) {
try {
$this->_descriptor = json_decode($source);
$this->descriptor = json_decode($source);
} catch (\Exception $e) {
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
throw new Exceptions\DatapackageInvalidSourceException(
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
);
}
$this->_basePath = $basePath;
} elseif ($this->_isHttpSource($source)) {
$this->basePath = $basePath;
} elseif ($this->isHttpSource($source)) {
try {
$this->_descriptor = json_decode(file_get_contents($this->_normalizeHttpSource($source)));
$this->descriptor = json_decode(file_get_contents($this->normalizeHttpSource($source)));
} catch (\Exception $e) {
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
throw new Exceptions\DatapackageInvalidSourceException(
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
);
}
// http sources don't allow relative paths, hence basePath should remain null
$this->_basePath = null;
$this->basePath = null;
} else {
if (empty($basePath)) {
$this->_basePath = dirname($source);
$this->basePath = dirname($source);
} else {
$this->_basePath = $basePath;
$absPath = $this->_basePath.DIRECTORY_SEPARATOR.$source;
$this->basePath = $basePath;
$absPath = $this->basePath.DIRECTORY_SEPARATOR.$source;
if (file_exists($absPath)) {
$source = $absPath;
}
}
try {
$this->_descriptor = json_decode(file_get_contents($source));
$this->descriptor = json_decode(file_get_contents($source));
} catch (\Exception $e) {
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
throw new Exceptions\DatapackageInvalidSourceException(
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
);
}

}
} else {
throw new DatapackageInvalidSourceException("Invalid source: ".json_encode($source));
throw new Exceptions\DatapackageInvalidSourceException(
"Invalid source: ".json_encode($source)
);
}
}

protected function _normalizeHttpSource($source)
public static function validate($source, $basePath=null)
{
return $source;
try {
$datapackage = new self($source, $basePath);
return DatapackageValidator::validate($datapackage->descriptor());
} catch (\Exception $e) {
return [new DatapackageValidationError(DatapackageValidationError::LOAD_FAILED, $e->getMessage())];
}

}

protected function _isHttpSource($source)
/**
* get the descriptor as a native PHP object
*
* @return object
*/
public function descriptor()
{
return Utils::is_http_source($source);
return $this->descriptor;
}

protected function _initResource($resourceDescriptor)
// standard iterator functions - to iterate over the resources
public function rewind() {$this->currentResourcePosition = 0;}
public function current() { return $this->initResource($this->descriptor()->resources[$this->currentResourcePosition]); }
public function key() { return $this->currentResourcePosition; }
public function next() { $this->currentResourcePosition++; }
public function valid() { return isset($this->descriptor()->resources[$this->currentResourcePosition]); }

protected $descriptor;
protected $currentResourcePosition = 0;
protected $basePath;

/**
* allows extending classes to add custom sources
* used by unit tests to add a mock http source
*
* @param string $source
* @return string
*/
protected function normalizeHttpSource($source)
{
return new Resource($resourceDescriptor, $this->_basePath);
return $source;
}

public function descriptor()
/**
* allows extending classes to add custom sources
* used by unit tests to add a mock http source
*
* @param string $source
* @return bool
*/
protected function isHttpSource($source)
{
return $this->_descriptor;
return Utils::isHttpSource($source);
}

// standard iterator functions - to iterate over the resources
public function rewind() { $this->_currentResourcePosition = 0; }
public function current() { return $this->_initResource($this->descriptor()->resources[$this->_currentResourcePosition]); }
public function key() { return $this->_currentResourcePosition; }
public function next() { $this->_currentResourcePosition++; }
public function valid() { return isset($this->descriptor()->resources[$this->_currentResourcePosition]); }
/**
* called by the resources iterator for each iteration
*
* @param object $resourceDescriptor
* @return \frictionlessdata\datapackage\Resource
*/
protected function initResource($resourceDescriptor)
{
return new Resource($resourceDescriptor, $this->basePath);
}
}


class DatapackageInvalidSourceException extends \Exception {};
8 changes: 8 additions & 0 deletions src/DatapackageValidationError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php namespace frictionlessdata\datapackage;

// TODO: move the SchemaValidation logic to it's own independent package
use frictionlessdata\tableschema;

class DatapackageValidationError extends tableschema\SchemaValidationError
{
}
32 changes: 32 additions & 0 deletions src/DatapackageValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php namespace frictionlessdata\datapackage;

// TODO: move the SchemaValidation logic to it's own independent package
use frictionlessdata\tableschema;

class DatapackageValidator extends tableschema\SchemaValidator
{
public static function validate($descriptor)
{
$validator = new self($descriptor);
return $validator->get_validation_errors();
}

protected function _validateSchema()
{
// Validate
$validator = new \JsonSchema\Validator();
$validator->validate(
$this->descriptor, (object)[
'$ref' => 'file://' . realpath(dirname(__FILE__)).'/schemas/data-package.json'
]
);
if (!$validator->isValid()) {
foreach ($validator->getErrors() as $error) {
$this->_addError(
DatapackageValidationError::SCHEMA_VIOLATION,
sprintf("[%s] %s", $error['property'], $error['message'])
);
}
}
}
}
6 changes: 6 additions & 0 deletions src/Exceptions/DataStreamOpenException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace frictionlessdata\datapackage\Exceptions;

class DataStreamOpenException extends \Exception {

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

class DatapackageInvalidSourceException extends \Exception {

}
Loading

0 comments on commit 34593be

Please sign in to comment.