Skip to content

Commit 34593be

Browse files
authored
Add basic validation support (#5)
* coding style fixes, minor refactoring * added DatapackageValidator (depends on SchemaValidator from frictionlessdata/tableschema) * added basic datapackage validation functionality
1 parent 9929166 commit 34593be

18 files changed

Lines changed: 949 additions & 205 deletions

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The project follows the [Open Knowledge International coding standards](https://
44

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

7+
Flow Framework has a nice guide regarding coding standards:
8+
* [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)
9+
* [The full guide **(.html)**](http://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartV/CodingGuideLines/PHP.html)
10+
711

812
## Getting Started
913

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ $ composer require frictionlessdata/datapackage
2323
```php
2424
use frictionlessdata\datapackage;
2525

26-
$datapackage = new Datapackage("tests/fixtures/multi_data_datapackage.json");
26+
// iterate over the data
27+
$datapackage = new datapackage\Datapackage("tests/fixtures/multi_data_datapackage.json");
2728
foreach ($datapackage as $resource) {
2829
print("-- ".$resource->name()." --");
2930
$i = 0;
@@ -34,6 +35,14 @@ foreach ($datapackage as $resource) {
3435
}
3536
}
3637
}
38+
39+
// validate the descriptor
40+
$validationErrors = datapackage\Datapackage::validate("tests/fixtures/simple_invalid_datapackage.json");
41+
if (count($validationErrors) == 0) {
42+
print("descriptor is valid");
43+
} else {
44+
print(datapackage\DatapackageValidationError::getErrorMessages($validationErrors));
45+
}
3746
```
3847

3948

composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"description": "A utility library for working with Data Packages",
44
"license": "MIT",
55
"require": {
6-
"php": ">=5.4"
6+
"php": ">=5.4",
7+
"justinrainbow/json-schema": "^5.2",
8+
"frictionlessdata/tableschema": "^0.1.2"
79
},
810
"require-dev": {
911
"phpunit/phpunit": "^4.8.35",
@@ -15,6 +17,6 @@
1517
}
1618
},
1719
"scripts": {
18-
"test": "phpunit --debug tests/ --coverage-clover coverage-clover.xml"
20+
"test": "phpunit --debug --coverage-clover coverage-clover.xml --bootstrap tests/autoload.php tests/"
1921
}
2022
}

src/DataStream.php

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
1-
<?php namespace frictionlessdata\datapackage;
2-
1+
<?php
2+
namespace frictionlessdata\datapackage;
33

44
class DataStream implements \Iterator
55
{
6-
protected $_currentLineNumber = 0;
7-
protected $_fopenResource;
8-
protected $_dataSource;
9-
106
public function __construct($dataSource)
117
{
128
try {
13-
$this->_fopenResource = fopen($dataSource, "r");
9+
$this->fopenResource = fopen($dataSource, "r");
1410
} catch (\Exception $e) {
15-
throw new DataStreamOpenException("Failed to open source ".json_encode($dataSource));
11+
throw new Exceptions\DataStreamOpenException("Failed to open source ".json_encode($dataSource).": ".json_encode($e->getMessage()));
1612
}
1713
}
1814

1915
public function __destruct()
2016
{
21-
fclose($this->_fopenResource);
17+
fclose($this->fopenResource);
2218
}
2319

2420
public function rewind() {
25-
if ($this->_currentLineNumber != 0) {
21+
if ($this->currentLineNumber != 0) {
2622
throw new \Exception("DataStream does not support rewind, sorry");
2723
}
2824
}
2925

3026
public function current() {
31-
$line = fgets($this->_fopenResource);
27+
$line = fgets($this->fopenResource);
3228
if ($line === false) {
3329
return "";
3430
} else {
@@ -37,17 +33,18 @@ public function current() {
3733
}
3834

3935
public function key() {
40-
return $this->_currentLineNumber;
36+
return $this->currentLineNumber;
4137
}
4238

4339
public function next() {
44-
$this->_currentLineNumber++;
40+
$this->currentLineNumber++;
4541
}
4642

4743
public function valid() {
48-
return (!feof($this->_fopenResource));
44+
return (!feof($this->fopenResource));
4945
}
50-
}
5146

52-
53-
class DataStreamOpenException extends \Exception {};
47+
protected $currentLineNumber = 0;
48+
protected $fopenResource;
49+
protected $dataSource;
50+
}

src/Datapackage.php

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<?php namespace frictionlessdata\datapackage;
2-
1+
<?php
2+
namespace frictionlessdata\datapackage;
33

44
/**
55
* Datapackage representation, supports loading from the following sources:
@@ -10,80 +10,121 @@
1010
*/
1111
class Datapackage implements \Iterator
1212
{
13-
protected $_descriptor;
14-
protected $_currentResourcePosition = 0;
15-
protected $_basePath;
16-
1713
public function __construct($source, $basePath=null)
1814
{
1915
if (is_object($source)) {
20-
$this->_descriptor = $source;
21-
$this->_basePath = $basePath;
16+
$this->descriptor = $source;
17+
$this->basePath = $basePath;
2218
} elseif (is_string($source)) {
23-
if (Utils::is_json_string($source)) {
19+
if (Utils::isJsonString($source)) {
2420
try {
25-
$this->_descriptor = json_decode($source);
21+
$this->descriptor = json_decode($source);
2622
} catch (\Exception $e) {
27-
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
23+
throw new Exceptions\DatapackageInvalidSourceException(
24+
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
25+
);
2826
}
29-
$this->_basePath = $basePath;
30-
} elseif ($this->_isHttpSource($source)) {
27+
$this->basePath = $basePath;
28+
} elseif ($this->isHttpSource($source)) {
3129
try {
32-
$this->_descriptor = json_decode(file_get_contents($this->_normalizeHttpSource($source)));
30+
$this->descriptor = json_decode(file_get_contents($this->normalizeHttpSource($source)));
3331
} catch (\Exception $e) {
34-
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
32+
throw new Exceptions\DatapackageInvalidSourceException(
33+
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
34+
);
3535
}
3636
// http sources don't allow relative paths, hence basePath should remain null
37-
$this->_basePath = null;
37+
$this->basePath = null;
3838
} else {
3939
if (empty($basePath)) {
40-
$this->_basePath = dirname($source);
40+
$this->basePath = dirname($source);
4141
} else {
42-
$this->_basePath = $basePath;
43-
$absPath = $this->_basePath.DIRECTORY_SEPARATOR.$source;
42+
$this->basePath = $basePath;
43+
$absPath = $this->basePath.DIRECTORY_SEPARATOR.$source;
4444
if (file_exists($absPath)) {
4545
$source = $absPath;
4646
}
4747
}
4848
try {
49-
$this->_descriptor = json_decode(file_get_contents($source));
49+
$this->descriptor = json_decode(file_get_contents($source));
5050
} catch (\Exception $e) {
51-
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
51+
throw new Exceptions\DatapackageInvalidSourceException(
52+
"Failed to load source: ".json_encode($source).": ".$e->getMessage()
53+
);
5254
}
5355

5456
}
5557
} else {
56-
throw new DatapackageInvalidSourceException("Invalid source: ".json_encode($source));
58+
throw new Exceptions\DatapackageInvalidSourceException(
59+
"Invalid source: ".json_encode($source)
60+
);
5761
}
5862
}
5963

60-
protected function _normalizeHttpSource($source)
64+
public static function validate($source, $basePath=null)
6165
{
62-
return $source;
66+
try {
67+
$datapackage = new self($source, $basePath);
68+
return DatapackageValidator::validate($datapackage->descriptor());
69+
} catch (\Exception $e) {
70+
return [new DatapackageValidationError(DatapackageValidationError::LOAD_FAILED, $e->getMessage())];
71+
}
72+
6373
}
6474

65-
protected function _isHttpSource($source)
75+
/**
76+
* get the descriptor as a native PHP object
77+
*
78+
* @return object
79+
*/
80+
public function descriptor()
6681
{
67-
return Utils::is_http_source($source);
82+
return $this->descriptor;
6883
}
6984

70-
protected function _initResource($resourceDescriptor)
85+
// standard iterator functions - to iterate over the resources
86+
public function rewind() {$this->currentResourcePosition = 0;}
87+
public function current() { return $this->initResource($this->descriptor()->resources[$this->currentResourcePosition]); }
88+
public function key() { return $this->currentResourcePosition; }
89+
public function next() { $this->currentResourcePosition++; }
90+
public function valid() { return isset($this->descriptor()->resources[$this->currentResourcePosition]); }
91+
92+
protected $descriptor;
93+
protected $currentResourcePosition = 0;
94+
protected $basePath;
95+
96+
/**
97+
* allows extending classes to add custom sources
98+
* used by unit tests to add a mock http source
99+
*
100+
* @param string $source
101+
* @return string
102+
*/
103+
protected function normalizeHttpSource($source)
71104
{
72-
return new Resource($resourceDescriptor, $this->_basePath);
105+
return $source;
73106
}
74107

75-
public function descriptor()
108+
/**
109+
* allows extending classes to add custom sources
110+
* used by unit tests to add a mock http source
111+
*
112+
* @param string $source
113+
* @return bool
114+
*/
115+
protected function isHttpSource($source)
76116
{
77-
return $this->_descriptor;
117+
return Utils::isHttpSource($source);
78118
}
79119

80-
// standard iterator functions - to iterate over the resources
81-
public function rewind() { $this->_currentResourcePosition = 0; }
82-
public function current() { return $this->_initResource($this->descriptor()->resources[$this->_currentResourcePosition]); }
83-
public function key() { return $this->_currentResourcePosition; }
84-
public function next() { $this->_currentResourcePosition++; }
85-
public function valid() { return isset($this->descriptor()->resources[$this->_currentResourcePosition]); }
120+
/**
121+
* called by the resources iterator for each iteration
122+
*
123+
* @param object $resourceDescriptor
124+
* @return \frictionlessdata\datapackage\Resource
125+
*/
126+
protected function initResource($resourceDescriptor)
127+
{
128+
return new Resource($resourceDescriptor, $this->basePath);
129+
}
86130
}
87-
88-
89-
class DatapackageInvalidSourceException extends \Exception {};

src/DatapackageValidationError.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
// TODO: move the SchemaValidation logic to it's own independent package
4+
use frictionlessdata\tableschema;
5+
6+
class DatapackageValidationError extends tableschema\SchemaValidationError
7+
{
8+
}

src/DatapackageValidator.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
// TODO: move the SchemaValidation logic to it's own independent package
4+
use frictionlessdata\tableschema;
5+
6+
class DatapackageValidator extends tableschema\SchemaValidator
7+
{
8+
public static function validate($descriptor)
9+
{
10+
$validator = new self($descriptor);
11+
return $validator->get_validation_errors();
12+
}
13+
14+
protected function _validateSchema()
15+
{
16+
// Validate
17+
$validator = new \JsonSchema\Validator();
18+
$validator->validate(
19+
$this->descriptor, (object)[
20+
'$ref' => 'file://' . realpath(dirname(__FILE__)).'/schemas/data-package.json'
21+
]
22+
);
23+
if (!$validator->isValid()) {
24+
foreach ($validator->getErrors() as $error) {
25+
$this->_addError(
26+
DatapackageValidationError::SCHEMA_VIOLATION,
27+
sprintf("[%s] %s", $error['property'], $error['message'])
28+
);
29+
}
30+
}
31+
}
32+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace frictionlessdata\datapackage\Exceptions;
3+
4+
class DataStreamOpenException extends \Exception {
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace frictionlessdata\datapackage\Exceptions;
3+
4+
class DatapackageInvalidSourceException extends \Exception {
5+
6+
}

0 commit comments

Comments
 (0)