Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#41 table relative paths #48

Merged
merged 18 commits into from
Jan 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,19 @@ new Datapackage('http://bit.do/datapackage-json', 'base', false).then(datapackag
- **profile** (defaults to `base`) is the validation profile to validate the descriptor against. Available profiles are `base`, `tabular` and `fiscal`, but you can also provide your own profile Object for validation.
- **raiseInvalid** (defaults to `true`) can be used to specify if you want a Array of descriptive errors to be throws if the datapacakge is invalid (or becomes invalid after modifying it), or to be able to work with datapackage which is in invalid state.
- **remoteProfiles** (defaults to `false`) can be used to specify if you want to work with the local copies of the profiles or fetch the latest profiles from Internet.
- **basePath** (defaults to `null`if the descriptor is an Object or a remote path, or it's the `dirname` of the local path) can be used to specify the base path for the resources defined in the descriptor. For example if the resource path is `data/resource.cvs` and the `basePath` is set to `datapackages/dp1` the resources are expected to be in `datapackages/dp1/data/resource.cvs` relative of the directory where the library is executed.
- **basePath** (defaults to empty string if the descriptor is an Object, the URL if it's a remote path or it's the `dirname` of the local path) can be used to specify the base path for the resources defined in the descriptor. Resources path is always appended to the `basePath`. The default behaviour of `basePath` is:
- If initialized with path to a local file
- default `basePath` is `dirname` of the path
- any explicitly provided `basePath` to the constructor is appended to the default `basepath`
- If initialized with a remote path
- default `basePath` is the remote path
- any explicitly provided `basePath` to the constructor is appended to the default `basePath`
- If initialized with `Object`
- default `basePath` is empty String (`''`)
- any explicit `basePath` will be used as `basePath`
- In case when the resource path is an absolute URL, `basePath` is disregarded and only the URL is used to fetch the resource.
- Examples
- `datapackage` is initialized with the `my-datapackages/datapackage.json` descriptor, the `basePath` is set to `data/` and the resource path is `november/resource.csv` the resource is expected to be in `my-datapackages/data/november/resource.cvs` relative of the directory where the library is executed.

###Class methods

Expand Down
4 changes: 4 additions & 0 deletions data/dp4-remote-resource/data/data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,size
gb,105
us,205
cn,305
60 changes: 60 additions & 0 deletions data/dp4-remote-resource/datapackage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "abc",
"resources": [
{
"name": "random",
"format": "csv",
"path": "https://raw.githubusercontent.com/frictionlessdata/datapackage-js/master/data/dp1/datapackage.json",
"schema": {
"fields": [
{
"name": "name",
"type": "string"
},
{
"name": "size",
"type": "number"
}
]
}
},
{
"name": "random",
"format": "csv",
"path": "data.csv",
"schema": {
"fields": [
{
"name": "name",
"type": "string"
},
{
"name": "size",
"type": "number"
}
]
}
}
],
"views": [
{
"type": "vegalite",
"spec": {
"data": {
"resource": "random"
},
"mark": "bar",
"encoding": {
"x": {
"field": "name",
"type": "ordinal"
},
"y": {
"field": "size",
"type": "quantitative"
}
}
}
}
]
}
86 changes: 66 additions & 20 deletions src/datapackage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash'
import path from 'path'
import url from 'url'
import Utils from './utils'
import Resource from './resource'
import Profiles from './profiles'
Expand All @@ -14,24 +15,32 @@ export default class DataPackage {
/**
* Returns a Promise that will resolve in Datapackage instance.
*
* @param {Object|String} descriptor - A datapackage descriptor Object or an URI string
* @param {Object|String} descriptor - A datapackage descriptor Object or an
* URI string
* @param {Object|String} [profile='base'] - Profile to validate against
* @param {Boolean} [raiseInvalid=true] - Throw errors if validation fails
* @param {Boolean} [remoteProfiles=false] - Use remote profiles
* @param {String|path} [basePath=null] - Base path for the resources. If the provided
* descriptor is a local path to a file, the default value is the dirname of the path.
* @param {String} [basePath=''] - Base path for the resources. If the
* provided descriptor is a local path to a file, the default value is the
* dirname of the path.
* @return {Promise} - Resolves in class instance or rejects with errors
* @throws Array of errors if raiseInvalid is true and basePath contains illegal characters
*/
constructor(descriptor, profile = 'base', raiseInvalid = true,
remoteProfiles = false, basePath) {
remoteProfiles = false, basePath = '') {

const self = this

return new Promise((resolve, reject) => {
self._profile = profile
self._raiseInvalid = raiseInvalid
self._remoteProfiles = remoteProfiles
self._basePath = basePath || self._getBasePath(descriptor)

// Check if basePath is valid and throw error if needed
const basePathErrors = Utils.checkPath(basePath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to always raise on bad base path. valid/errors/raiseInvalid is only for descriptor validation.

const basePathValid = basePathErrors.length === 0
if (!basePathValid) throw basePathErrors
self._basePath = DataPackage._getBasePath(descriptor, basePath)

new Profiles(self._remoteProfiles).then(profilesInstance => {
self._Profiles = profilesInstance
Expand Down Expand Up @@ -88,7 +97,8 @@ export default class DataPackage {
/**
* Updates the current descriptor with the properties of the provided Object.
* New properties are added and existing properties are replaced.
* Note: it doesn't do deep merging, it just adds/replaces top level properties
* Note: it doesn't do deep merging, it just adds/replaces top level
* properties
*
* @param {Object} newDescriptor
* @return {Boolean} - Returns validation status of the package
Expand All @@ -111,17 +121,18 @@ export default class DataPackage {
}

/**
* Adds new resource to the datapackage and triggers validation of the datapackage.
* When adding a resource that is already present in the datapackage, the
* provided resource will be omitted and the return value will be `true`.
* Adds new resource to the datapackage and triggers validation of the
* datapackage. When adding a resource that is already present in the
* datapackage, the provided resource will be omitted and the return value
* will be `true`.
*
* @param descriptor {Object}
* @returns {Boolean} - Returns validation status of the package
* @throws {Array} - Will throw Array of errors if validations fails and
* raiseInvalid is `true` or descriptor argument is not an Object
*/
addResource(descriptor) {
if (_.isObject(descriptor) && !_.isFunction(descriptor)) {
if (_.isObject(descriptor) && !_.isFunction(descriptor) && !_.isArray(descriptor)) {
const newResource = new Resource(descriptor, this._basePath)
const resourceFound = _.find(
this.resources, resource => _.isEqual(resource, newResource))
Expand Down Expand Up @@ -154,13 +165,45 @@ export default class DataPackage {
* @private
*/
_validateDescriptor(descriptor, profile) {
const validation = this._Profiles.validate(descriptor, profile)
const validationError = validation instanceof Array
const descriptorErrors = this._Profiles.validate(descriptor, profile)
if (descriptorErrors instanceof Array) {
this._errors = descriptorErrors
this._valid = false
} else {
this._valid = true
}

_.forEach(descriptor.resources, resource => {
this._valid = this.valid && this._validateResource(resource)
})

this._errors = validationError ? validation : []
this._valid = !validationError
return this.valid
}

return this._valid
/**
* Validate a resource descriptor. It returns the validity of the resource
* and store any errors in this._errors.
*
* @param {Object} resource
* @return {boolean}
* @private
*/
_validateResource(resource) {
const resourceObject = new Resource(resource, this._basePath)

let pathErrors = []
if (resourceObject.type !== 'inline') {
try {
const valid = resourceObject._validPaths
} catch (err) {
pathErrors = err
}
}

const pathValid = pathErrors.length === 0
this._errors = this.errors.concat(pathErrors)

return pathValid
}

/**
Expand Down Expand Up @@ -234,19 +277,22 @@ export default class DataPackage {

/**
* Returns the basepath from the path of the current descriptor if it is a
* local path.
* local path, or the URL if the datapackage was loaded via URL.
*
* @param {String} descriptor
* @param {String} basePath
* @return {String|null}
* @private
*/
_getBasePath(descriptor) {
static _getBasePath(descriptor, basePath = '') {
if (typeof descriptor === 'string') {
if (!Utils.isRemoteURL(descriptor)) {
return path.dirname(descriptor)
if (Utils.isRemoteURL(descriptor)) {
return url.resolve(descriptor, basePath)
}

return path.join(path.dirname(descriptor), basePath)
}

return null
return basePath
}
}
4 changes: 2 additions & 2 deletions src/profiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export default class Profiles {
* @param {Object} descriptor The descriptor that needs to be validated
* @param {Object|String} profile Schema to validate against, could be ID of a
* profile or profile Object
* @return {true|Array} Resolves `true` or array of strings which explain the
* errors.
* @return {Array} Empty array or array of errors found
*
*/
validate(descriptor, profile = 'base') {
function _tv4validation(data, schema) {
Expand Down
Loading