Skip to content

Commit

Permalink
feat(data): Add data query
Browse files Browse the repository at this point in the history
Added a data query object with all parameters allowed by the SDMX RESTful API
  • Loading branch information
sosna committed Mar 4, 2016
1 parent 23bb2f9 commit 17989af
Show file tree
Hide file tree
Showing 4 changed files with 508 additions and 15 deletions.
105 changes: 105 additions & 0 deletions src/data/data-query.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{DataDetail} = require './data-detail.coffee'
{FlowRefType, SeriesKeyType, ProviderRefType, NCNameIDType} =
require '../utils/sdmx-patterns.coffee'
{isValidEnum, isValidPattern, isValidPeriod, isValidDate, createErrorMessage} =
require '../utils/validators.coffee'

defaults =
key: 'all'
provider: 'all'
obsDimension: 'TIME_PERIOD'
detail: DataDetail.FULL
history: false

isValidHistory = (input, errors) ->
valid = typeof input is 'boolean'
if not valid
errors.push "#{input} is not a valid value for history. Must be true or \
false"
valid

isValidNObs = (input, name, errors) ->
valid = typeof input is 'number' and input > 0
if not valid
errors.push "#{input} is not a valid value for #{name}. Must be a positive \
integer"
valid

validQuery = (q) ->
errors = []
isValid = isValidPattern(q.flow, FlowRefType, 'flows', errors) and
isValidPattern(q.key, SeriesKeyType, 'series key', errors) and
isValidPattern(q.provider, ProviderRefType, 'provider', errors) and
(!q.start or isValidPeriod(q.start, 'start period', errors)) and
(!q.end or isValidPeriod(q.end, 'end period', errors)) and
(!q.updatedAfter or isValidDate(q.updatedAfter, 'updatedAfter', errors)) and
(!q.firstNObs or isValidNObs(q.firstNObs, 'firstNObs', errors)) and
(!q.lastNObs or isValidNObs(q.lastNObs, 'lastNObs', errors)) and
isValidPattern(q.obsDimension, NCNameIDType, 'obs dimension', errors) and
isValidEnum(q.detail, DataDetail, 'details', errors) and
isValidHistory(q.history, errors)
{isValid: isValid, errors: errors}

# A query for data, as defined by the SDMX RESTful API.
query = class DataQuery

defaults: Object.freeze defaults

constructor: (@flow) ->

key: (@series) ->
@

provider: (@providerRef) ->
@

start: (@startPeriod) ->
@

end: (@endPeriod) ->
@

updatedAfter: (@lastQuery) ->
@

firstNObs: (@firstN) ->
@

lastNObs: (@lastN) ->
@

obsDimension: (@dim) ->
@

detail: (@info) ->
@

history: (@hist) ->
@

build: () ->
query =
flow: @flow
key: @series ? defaults.key
provider: @providerRef ? defaults.provider
start: @startPeriod
end: @endPeriod
updatedAfter: @lastQuery
firstNObs: @firstN
lastNObs: @lastN
obsDimension: @dim ? defaults.obsDimension
detail: @info ? defaults.detail
history: @hist ? defaults.history
input = validQuery query
throw Error createErrorMessage(input.errors, 'data query') \
unless input.isValid
query

@from: (options) ->
new DataQuery(options?.flow).key(options?.key).provider(options?.provider)
.start(options?.start).end(options?.end)
.updatedAfter(options?.updatedAfter).firstNObs(options?.firstNObs)
.lastNObs(options?.lastNObs).obsDimension(options?.obsDimension)
.detail(options?.detail).history(options?.history).build()

exports.DataQuery = query
93 changes: 79 additions & 14 deletions src/utils/sdmx-patterns.coffee
Original file line number Diff line number Diff line change
@@ -1,27 +1,92 @@
NestedNCNameIDType = /// ^
NCNameIDType = ///
[A-Za-z] # Must begin with a letter
[A-Za-z0-9_\-]* # May be followed by letters, numbers, _ or -
(\.[A-Za-z][A-Za-z0-9_\-]*)* # May be followed by a dot and other IDs
///

NCNameIDTypeAlone = /// ^
#{NCNameIDType.source}
$ ///

NestedNCNameIDType = ///
#{NCNameIDType.source} # An ID
(
\. # May be followed by a dot and other IDs
#{NCNameIDType.source}
)*
///

NestedNCNameIDTypeAlone = /// ^
#{NestedNCNameIDType.source}
$ ///

IDType = /// ^
IDType = ///
[A-Za-z0-9_@$\-]+ # Letters, numbers, _, @, $ or -
///

IDTypeAlone = /// ^
#{IDType.source}
$ ///

VersionType = /// ^
( # Starts the OR clause
all # The string all
| latest # Or the string latest
| [0-9]+(\.[0-9]+)* # Or a version number (e.g. 1.0)
) # Ends the OR clause
VersionNumber = ///
[0-9]+(\.[0-9]+)* # A version number (e.g. 1.0)
///

VersionType = ///
( # Starts the OR clause
all # The string all
| latest # Or the string latest
| #{VersionNumber.source} # Or a version number
) # Ends the OR clause
///

VersionTypeAlone = /// ^
#{VersionType.source}
$ ///

NestedIDType = /// ^
NestedIDType = ///
[A-Za-z0-9_@$\-]+ # Letters, numbers, _, @, $ or -
(\.[A-Za-z0-9_@$\-]+)* # Potentially hierarchical (e.g. A.B.C)
///

NestedIDTypeAlone = /// ^
#{NestedIDType.source}
$ ///

SeriesKeyType = /// ^
(#{IDType.source}([+]#{IDType.source})*)? # One or more dimension values
# separated by a +
(
[.] # Potentially followed by a dot
(#{IDType.source}([+]#{IDType.source})*)? # and repeating above pattern
)*
$ ///

FlowRefType = /// ^
(
#{IDType.source}
| (
#{NestedNCNameIDType.source}
\,#{IDType.source}
(\,(latest | (#{VersionNumber.source})))?
)
)
$ ///

ProviderRefType = /// ^
(#{NestedNCNameIDType.source},)? # May start with the agency owning the scheme
#{IDType.source} # The id of the provider
$ ///

ReportingPeriodType = /// ^
\d{4}\-([ASTQ]\d{1}|[MW]\d{2}|[D]\d{3})
$ ///

exports.NestedNCNameIDType = NestedNCNameIDType
exports.IDType = IDType
exports.VersionType = VersionType
exports.NestedIDType = NestedIDType
exports.NCNameIDType = NCNameIDTypeAlone
exports.NestedNCNameIDType = NestedNCNameIDTypeAlone
exports.IDType = IDTypeAlone
exports.VersionType = VersionTypeAlone
exports.NestedIDType = NestedIDTypeAlone
exports.FlowRefType = FlowRefType
exports.ProviderRefType = ProviderRefType
exports.ReportingPeriodType = ReportingPeriodType
exports.SeriesKeyType = SeriesKeyType
21 changes: 20 additions & 1 deletion src/utils/validators.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{ReportingPeriodType} = require './sdmx-patterns.coffee'

validEnum = (input, list, name, errors) ->
found = false
for key, value of list
Expand All @@ -8,7 +10,7 @@ validEnum = (input, list, name, errors) ->
found

validPattern = (input, regex, name, errors) ->
valid = input.match regex
valid = input and input.match regex
if not valid
errors.push "#{input} is not compliant with the pattern defined for \
#{name} (#{regex})"
Expand All @@ -20,6 +22,23 @@ createErrorMessage = (errors, type) ->
msg += "- #{error} \n"
msg

validIso8601 = (input, name, errors) ->
valid = true
if isNaN(Date.parse(input))
errors.push "#{name} must be a valid ISO8601 date"
valid = false
valid

validPeriod = (input, name, errors) ->
valid = validIso8601(input, name, errors) \
or validPattern(input, ReportingPeriodType, name, errors)
if not valid
errors.push "#{name} must be a valid SDMX period or a valid ISO8601 date"
valid = false
valid

exports.isValidEnum = validEnum
exports.isValidPattern = validPattern
exports.createErrorMessage = createErrorMessage
exports.isValidDate = validIso8601
exports.isValidPeriod = validPeriod
Loading

0 comments on commit 17989af

Please sign in to comment.