From 3c2e964adbdbdff401485c7a00cc47fdbef34c79 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 10:53:41 +0200 Subject: [PATCH 01/47] Refactor URL generator tests --- .../url-generator-availability.test.coffee | 257 +++ test/utils/url-generator-data.test.coffee | 477 +++++ test/utils/url-generator-metadata.test.coffee | 813 ++++++++ test/utils/url-generator-schema.test.coffee | 127 ++ test/utils/url-generator.test.coffee | 1649 +---------------- 5 files changed, 1675 insertions(+), 1648 deletions(-) create mode 100644 test/utils/url-generator-availability.test.coffee create mode 100644 test/utils/url-generator-data.test.coffee create mode 100644 test/utils/url-generator-metadata.test.coffee create mode 100644 test/utils/url-generator-schema.test.coffee diff --git a/test/utils/url-generator-availability.test.coffee b/test/utils/url-generator-availability.test.coffee new file mode 100644 index 0000000..59093b3 --- /dev/null +++ b/test/utils/url-generator-availability.test.coffee @@ -0,0 +1,257 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{AvailabilityQuery} = require '../../src/avail/availability-query' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for availability queries', -> + + it 'generates a URL for a full availability query', -> + expected = 'http://test.com/availableconstraint/EXR/A..EUR.SP00.A/ECB/FREQ?\ + mode=available&references=none\ + &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + provider: 'ECB' + component: 'FREQ' + start: '2010' + end: '2015' + updatedAfter: '2016-03-01T00:00:00Z' + mode: 'available' + references: 'none' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a full availability query (2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ + mode=available&references=none\ + &updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + component: 'FREQ' + updatedAfter: '2016-03-01T00:00:00Z' + mode: 'available' + references: 'none' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial availability query', -> + expected = 'http://test.com/availableconstraint/EXR/A..EUR.SP00.A/all/all?\ + mode=exact&references=none' + query = AvailabilityQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial availability query (2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/*?\ + mode=exact&references=none' + query = AvailabilityQuery.from({flow: 'EXR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'supports minimal query if proper query class is used', -> + expected = 'http://test.com/availableconstraint/EXR/all/all/all?\ + mode=exact&references=none' + query = AvailabilityQuery.from({flow: 'EXR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support availability queries before v1.3.0', -> + query = AvailabilityQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) + service = Service.from({url: 'http://test.com', api: 'v1.2.0'}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Availability query not supported in v1.2.0') + + it 'offers to skip default values for availability', -> + expected = 'http://test.com/availableconstraint/EXR' + query = AvailabilityQuery.from({ + flow: 'EXR' + mode: 'exact' + references: 'none' + }) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values for availability (2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR' + query = AvailabilityQuery.from({ + flow: 'EXR' + mode: 'exact' + references: 'none' + }) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (key, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A.CH' + query = AvailabilityQuery.from({flow: 'EXR', key: 'A.CH'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (provider)', -> + expected = 'http://test.com/availableconstraint/EXR/all/ECB' + query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (component)', -> + expected = 'http://test.com/availableconstraint/EXR/all/all/FREQ' + query = AvailabilityQuery.from({flow: 'EXR', component: 'FREQ'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (component, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/FREQ' + query = AvailabilityQuery.from({flow: 'EXR', component: 'FREQ'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (mode)', -> + expected = 'http://test.com/availableconstraint/EXR?mode=available' + query = AvailabilityQuery.from({flow: 'EXR', mode: 'available'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (mode, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + ?mode=available' + query = AvailabilityQuery.from({flow: 'EXR', mode: 'available'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (refs)', -> + expected = 'http://test.com/availableconstraint/EXR?references=codelist' + query = AvailabilityQuery.from({flow: 'EXR', references: 'codelist'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (refs, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + ?references=codelist' + query = AvailabilityQuery.from({flow: 'EXR', references: 'codelist'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (start)', -> + expected = 'http://test.com/availableconstraint/EXR?startPeriod=2007' + query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (end)', -> + expected = 'http://test.com/availableconstraint/EXR?endPeriod=2073' + query = AvailabilityQuery.from({flow: 'EXR', end: '2073'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (start & end)', -> + expected = 'http://test.com/availableconstraint/EXR?startPeriod=2007&\ + endPeriod=2073' + query = AvailabilityQuery.from({flow: 'EXR', start: '2007', end: '2073'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (upd)', -> + expected = 'http://test.com/availableconstraint/EXR?\ + updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery.from({ + flow: 'EXR' + updatedAfter: '2016-03-01T00:00:00Z'}) + service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (upd, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery.from({ + flow: 'EXR' + updatedAfter: '2016-03-01T00:00:00Z'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (multi, 2.0.0)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + mode=available&updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery.from({ + flow: 'EXR' + updatedAfter: '2016-03-01T00:00:00Z', + mode: 'available' + }) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'throws an error when using provider with 2.0.0', -> + query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'provider not allowed in v2.0.0') + + query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'provider not allowed in v2.0.0') + + it 'throws an error when using start with 2.0.0', -> + query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'start not allowed in v2.0.0') + + query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'start not allowed in v2.0.0') + + it 'throws an error when using end with 2.0.0', -> + query = AvailabilityQuery.from({flow: 'EXR', end: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'end not allowed in v2.0.0') + + query = AvailabilityQuery.from({flow: 'EXR', end: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'end not allowed in v2.0.0') + + it 'rejects keys containing dimensions separated with + (2.0.0)', -> + query = AvailabilityQuery.from({ + flow: 'ECB,EXR,1.42' + key: 'A+M.CHF' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, '+ not allowed in key in v2.0.0') + + query = AvailabilityQuery.from({ + flow: 'ECB,EXR,1.42' + key: 'A+M.CHF' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service, true) + should.Throw(test, Error, '+ not allowed in key in v2.0.0') diff --git a/test/utils/url-generator-data.test.coffee b/test/utils/url-generator-data.test.coffee new file mode 100644 index 0000000..6ef933e --- /dev/null +++ b/test/utils/url-generator-data.test.coffee @@ -0,0 +1,477 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{DataQuery} = require '../../src/data/data-query' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for data queries', -> + + it 'generates a URL for a full data query', -> + expected = "http://test.com/data/EXR/A..EUR.SP00.A/ECB?\ + dimensionAtObservation=CURRENCY&detail=nodata&includeHistory=true\ + &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z\ + &firstNObservations=1&lastNObservations=1" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + provider: 'ECB' + obsDimension: 'CURRENCY' + detail: 'nodata' + history: true + start: '2010' + end: '2015' + updatedAfter: '2016-03-01T00:00:00Z' + firstNObs: 1 + lastNObs: 1 + }) + service = Service.from({ + url: 'http://test.com' + api: ApiVersion.v1_1_0 + }) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a full data query (2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A\ + ?dimensionAtObservation=CURRENCY\ + &attributes=dataset,series&measures=none\ + &includeHistory=true\ + &updatedAfter=2016-03-01T00:00:00Z\ + &firstNObservations=1&lastNObservations=1" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + obsDimension: 'CURRENCY' + detail: 'nodata' + history: true + updatedAfter: '2016-03-01T00:00:00Z' + firstNObs: 1 + lastNObs: 1 + }) + service = Service.from({ + url: 'http://test.com' + api: ApiVersion.v2_0_0 + }) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial data query', -> + expected = "http://test.com/data/EXR/A..EUR.SP00.A/all?\ + detail=full&includeHistory=false" + query = DataQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial data query (2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'supports history but only for API version 1.1.0 and above', -> + expected = "http://test.com/data/EXR/A..EUR.SP00.A/ECB?\ + dimensionAtObservation=CURRENCY&detail=nodata\ + &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z\ + &firstNObservations=1&lastNObservations=1" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + provider: 'ECB' + obsDimension: 'CURRENCY' + detail: 'nodata' + history: true + start: '2010' + end: '2015' + updatedAfter: '2016-03-01T00:00:00Z' + firstNObs: 1 + lastNObs: 1 + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'defaults to latest API', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ + dimensionAtObservation=CURRENCY&attributes=dataset,series&measures=none\ + &includeHistory=true&updatedAfter=2016-03-01T00:00:00Z\ + &firstNObservations=1&lastNObservations=1" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + obsDimension: 'CURRENCY' + detail: 'nodata' + history: true + updatedAfter: '2016-03-01T00:00:00Z' + firstNObs: 1 + lastNObs: 1 + }) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'offers to skip default values for data', -> + expected = "http://test.com/data/EXR" + query = DataQuery.from({flow: 'EXR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values for data (v2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR" + query = DataQuery.from({flow: 'EXR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (provider)', -> + expected = "http://test.com/data/EXR/all/ECB" + query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (start)', -> + expected = "http://test.com/data/EXR?startPeriod=2010" + query = DataQuery.from({flow: 'EXR', start: '2010'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (end)', -> + expected = "http://test.com/data/EXR?endPeriod=2010" + query = DataQuery.from({flow: 'EXR', end: '2010'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (updatedAfter)', -> + expected = "http://test.com/data/EXR?updatedAfter=2016-03-01T00:00:00Z" + query = DataQuery.from({ + flow: 'EXR' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (updatedAfter, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?updatedAfter=2016-03-01T00:00:00Z" + query = DataQuery.from({ + flow: 'EXR' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (firstNObs)', -> + expected = "http://test.com/data/EXR?firstNObservations=1" + query = DataQuery.from({flow: 'EXR', firstNObs: 1}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (firstNObs, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?firstNObservations=1" + query = DataQuery.from({flow: 'EXR', firstNObs: 1}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (lastNObs)', -> + expected = "http://test.com/data/EXR?lastNObservations=2" + query = DataQuery.from({flow: 'EXR', lastNObs: 2}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (lastNObs, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?lastNObservations=2" + query = DataQuery.from({flow: 'EXR', lastNObs: 2}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (detail)', -> + expected = "http://test.com/data/EXR?detail=dataonly" + query = DataQuery.from({flow: 'EXR', detail: 'dataonly'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (detail, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?attributes=none&measures=all" + query = DataQuery.from({flow: 'EXR', detail: 'dataonly'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (history)', -> + expected = "http://test.com/data/EXR?includeHistory=true" + query = DataQuery.from({flow: 'EXR', history: true}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (history, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?includeHistory=true" + query = DataQuery.from({flow: 'EXR', history: true}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (obsDim)', -> + expected = "http://test.com/data/EXR?dimensionAtObservation=CURR" + query = DataQuery.from({flow: 'EXR', obsDimension: 'CURR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (obsDim, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR?dimensionAtObservation=CURR" + query = DataQuery.from({flow: 'EXR', obsDimension: 'CURR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (various)', -> + expected = "http://test.com/data/EXR/A..EUR.SP00.A?\ + updatedAfter=2016-03-01T00:00:00Z\ + &startPeriod=2010&dimensionAtObservation=CURRENCY" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + obsDimension: 'CURRENCY' + start: '2010' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (various, 2.0.0)', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ + updatedAfter=2016-03-01T00:00:00Z\ + &dimensionAtObservation=CURRENCY" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + obsDimension: 'CURRENCY' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports multiple providers for API version 1.3.0 and until 2.0.0', -> + expected = "http://test.com/data/EXR/A..EUR.SP00.A/SDMX,ECB+BIS?\ + updatedAfter=2016-03-01T00:00:00Z\ + &startPeriod=2010&dimensionAtObservation=CURRENCY" + query = DataQuery.from({ + flow: 'EXR' + key: 'A..EUR.SP00.A' + obsDimension: 'CURRENCY' + start: '2010' + updatedAfter: '2016-03-01T00:00:00Z' + provider: ['SDMX,ECB', 'BIS'] + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support providers before API version 1.3.0', -> + query = DataQuery.from({flow: 'EXR', provider: 'SDMX,ECB+BIS'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Multiple providers not allowed in v1.2.0') + + it 'throws an error when using provider with 2.0.0', -> + query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'provider not allowed in v2.0.0') + + query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'provider not allowed in v2.0.0') + + it 'throws an error when using start with 2.0.0', -> + query = DataQuery.from({flow: 'EXR', start: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'start not allowed in v2.0.0') + + query = DataQuery.from({flow: 'EXR', start: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'start not allowed in v2.0.0') + + it 'throws an error when using end with 2.0.0', -> + query = DataQuery.from({flow: 'EXR', end: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service + should.Throw(test, Error, 'end not allowed in v2.0.0') + + query = DataQuery.from({flow: 'EXR', end: '2007'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl query, service, true + should.Throw(test, Error, 'end not allowed in v2.0.0') + + it 'translates details=full with 2.0.0', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery.from({ + flow: 'EXR' + detail: 'full' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/*/EXR" + query = DataQuery.from({ + flow: 'EXR' + detail: 'full' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates details=dataonly with 2.0.0', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=none&measures=all&includeHistory=false" + query = DataQuery.from({ + flow: 'EXR' + detail: 'dataonly' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/*/EXR?\ + attributes=none&measures=all" + query = DataQuery.from({ + flow: 'EXR' + detail: 'dataonly' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates details=serieskeysonly with 2.0.0', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=none&measures=none&includeHistory=false" + query = DataQuery.from({ + flow: 'EXR' + detail: 'serieskeysonly' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/*/EXR?\ + attributes=none&measures=none" + query = DataQuery.from({ + flow: 'EXR' + detail: 'serieskeysonly' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates details=nodata with 2.0.0', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=dataset,series&measures=none&includeHistory=false" + query = DataQuery.from({ + flow: 'EXR' + detail: 'nodata' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/*/EXR?\ + attributes=dataset,series&measures=none" + query = DataQuery.from({ + flow: 'EXR' + detail: 'nodata' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates 1-part dataflow in the correct 2.0.0 context', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery.from({ + flow: 'EXR' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/*/EXR" + query = DataQuery.from({ + flow: 'EXR' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates 2-parts dataflow in the correct 2.0.0 context', -> + expected = "http://test.com/data/dataflow/ECB/EXR/*/*?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery.from({ + flow: 'ECB,EXR' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/ECB/EXR" + query = DataQuery.from({ + flow: 'ECB,EXR' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'translates 3-parts dataflow in the correct 2.0.0 context', -> + expected = "http://test.com/data/dataflow/ECB/EXR/1.42/*?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery.from({ + flow: 'ECB,EXR,1.42' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/data/dataflow/ECB/EXR/1.42" + query = DataQuery.from({ + flow: 'ECB,EXR,1.42' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'rejects keys containing dimensions separated with + (2.0.0)', -> + query = DataQuery.from({ + flow: 'ECB,EXR,1.42' + key: 'A+M.CHF' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, '+ not allowed in key in v2.0.0') + + query = DataQuery.from({ + flow: 'ECB,EXR,1.42' + key: 'A+M.CHF' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service, true) + should.Throw(test, Error, '+ not allowed in key in v2.0.0') diff --git a/test/utils/url-generator-metadata.test.coffee b/test/utils/url-generator-metadata.test.coffee new file mode 100644 index 0000000..f0bc86e --- /dev/null +++ b/test/utils/url-generator-metadata.test.coffee @@ -0,0 +1,813 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{MetadataQuery} = require '../../src/metadata/metadata-query' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for metadata queries', -> + + it 'generates a URL for a metadata query', -> + expected = "http://sdw-wsrest.ecb.europa.eu/service/codelist/ECB/CL_FREQ/\ + latest?detail=full&references=none" + query = + MetadataQuery.from({resource: 'codelist', id: 'CL_FREQ', agency: 'ECB'}) + service = Service.ECB + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a metadata ItemScheme query', -> + expected = "http://test.com/service/codelist/all/all/\ + latest/all?detail=full&references=none" + query = MetadataQuery.from({resource: 'codelist'}) + service = Service.from( + {url: "http://test.com/service/", api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a metadata non-ItemScheme query', -> + expected = "http://test.com/service/dataflow/all/all/\ + latest?detail=full&references=none" + query = MetadataQuery.from({resource: 'dataflow'}) + service = Service.from( + {url: "http://test.com/service/", api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'supports item queries for API version 1.1.0 and above', -> + expected = "http://test.com/codelist/ECB/CL_FREQ/latest/A\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + item: 'A' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support item queries before API version 1.1.0', -> + expected = "http://test.com/codelist/ECB/CL_FREQ/latest\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + item: 'A' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'treats hierarchical codelists as item schemes for API version 1.2.0', -> + expected = "http://test.com/hierarchicalcodelist/BIS/HCL/latest/HIERARCHY\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'hierarchicalcodelist' + id: 'HCL' + agency: 'BIS' + item: 'HIERARCHY' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'hierarchicalcodelist not allowed in v2.0.0') + + it 'does not support hiearchy queries before API version 1.2.0', -> + expected = "http://test.com/hierarchicalcodelist/BIS/HCL/latest\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'hierarchicalcodelist' + id: 'HCL' + agency: 'BIS' + item: 'HIERARCHY' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'Does not support multiple artefact types before API version 2.0.0', -> + query = MetadataQuery.from({resource: 'codelist+dataflow'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'codelist+dataflow not allowed in v1.5.0') + + it 'Supports multiple artefact types since API 2.0.0', -> + expected = "http://test.com/structure/codelist,dataflow/*/*/~\ + ?detail=full&references=none" + query = MetadataQuery.from({resource: 'codelist,dataflow'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist,dataflow" + query = MetadataQuery.from({resource: 'codelist,dataflow'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites + for multiple artefact types in API 2.0.0', -> + expected = "http://test.com/structure/codelist,dataflow/*/*/~\ + ?detail=full&references=none" + query = MetadataQuery.from({resource: 'codelist+dataflow'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist,dataflow" + query = MetadataQuery.from({resource: 'codelist+dataflow'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Supports all for artefact types via * since API 2.0.0', -> + expected = "http://test.com/structure/*/*/*/~\ + ?detail=full&references=none" + query = MetadataQuery.from({resource: '*'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/*" + query = MetadataQuery.from({resource: '*'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports multiple agencies for API version 1.3.0 and above', -> + expected = "http://test.com/codelist/ECB+BIS/CL_FREQ/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB+BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ/~/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB,BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + + it 'does not support multiple agencies before API version 1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB+BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Multiple agencies not allowed in v1.2.0') + + it 'Rewrites , for multiple agencies before API version 2.0.0', -> + expected = "http://test.com/codelist/ECB+BIS/CL_FREQ/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB,BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'Rewrites + for multiple agencies since API 2.0.0', -> + expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ/~/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB+BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB+BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites * for agencies before API version 2.0.0', -> + expected = "http://test.com/codelist/all/CL_FREQ/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: '*' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/codelist/all/CL_FREQ" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: '*' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites all for agencies since API 2.0.0', -> + expected = "http://test.com/structure/codelist/*/CL_FREQ/~/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'all' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/*/CL_FREQ" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'all' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports multiple IDs for API version 1.3.0 and above', -> + expected = "http://test.com/codelist/ECB/CL_FREQ+CL_DECIMALS/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ+CL_DECIMALS' + agency: 'ECB' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support multiple IDs before API version 1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ+CL_DECIMALS' + agency: 'ECB' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Multiple IDs not allowed in v1.2.0') + + it 'Rewrites , for multiple resource IDs before API version 2.0.0', -> + expected = "http://test.com/codelist/BIS/CL_FREQ+CL_DECIMALS/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ,CL_DECIMALS' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/codelist/BIS/CL_FREQ+CL_DECIMALS" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ,CL_DECIMALS' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites + for multiple resource IDs since API 2.0.0', -> + expected = "http://test.com/structure/codelist/BIS/CL_FREQ,CL_UNIT/~/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ+CL_UNIT' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/BIS/CL_FREQ,CL_UNIT" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ+CL_UNIT' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites * for resource IDs before API version 2.0.0', -> + expected = "http://test.com/codelist/BIS/all/latest/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: '*' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/codelist/BIS/all/1.0" + query = MetadataQuery.from({ + resource: 'codelist' + id: '*' + agency: 'BIS' + version: '1.0' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites all for resources IDs since API 2.0.0', -> + expected = "http://test.com/structure/codelist/BIS/*/~/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'all' + agency: 'BIS' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/BIS/*/1.0" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'all' + agency: 'BIS' + version: '1.0' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports multiple versions for API version 1.3.0 and above', -> + expected = "http://test.com/codelist/ECB/CL_FREQ/1.0+1.1/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + version: '1.0+1.1' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/ECB/CL_FREQ/1.0.0,1.1.0/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + version: '1.0.0,1.1.0' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support multiple versions before API version 1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + version: '1.0+1.1' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Multiple versions not allowed in v1.1.0') + + it 'supports multiple items for API version 1.3.0 and above', -> + expected = "http://test.com/codelist/ECB/CL_FREQ/1.0/A+M\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + version: '1.0' + item: 'A+M' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support multiple items before API version 1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'ECB' + version: '1.0' + item: 'A+M' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Multiple items not allowed in v1.2.0') + + it 'Rewrites + for multiple items since API 2.0.0', -> + expected = "http://test.com/structure/codelist/BIS/CL_FREQ/~/A,M\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + item: 'A+M' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/BIS/CL_FREQ/~/A,M" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + item: 'A+M' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites * for items before API version 2.0.0', -> + expected = "http://test.com/codelist/BIS/CL_FREQ/1.0/all\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + version: '1.0' + item: '*' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/codelist/BIS/CL_FREQ/1.0" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + version: '1.0' + item: '*' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'Rewrites all for items since API 2.0.0', -> + expected = "http://test.com/structure/codelist/BIS/CL_FREQ/1.0/*\ + ?detail=full&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + version: '1.0' + item: 'all' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist/BIS/CL_FREQ/1.0" + query = MetadataQuery.from({ + resource: 'codelist' + id: 'CL_FREQ' + agency: 'BIS' + version: '1.0' + item: 'all' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values for metadata', -> + expected = "http://test.com/codelist" + query = MetadataQuery.from({resource: 'codelist'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (id)', -> + expected = "http://test.com/codelist/all/CL_FREQ" + query = MetadataQuery.from({resource: 'codelist', id: 'CL_FREQ'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (version)', -> + expected = "http://test.com/codelist/all/all/42" + query = MetadataQuery.from({resource: 'codelist', version: '42'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (item)', -> + expected = "http://test.com/codelist/all/all/latest/1" + query = MetadataQuery.from({resource: 'codelist', item: '1'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (item, old API)', -> + expected = "http://test.com/codelist" + query = MetadataQuery.from({resource: 'codelist', item: '1'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (detail)', -> + expected = "http://test.com/codelist?detail=allstubs" + query = MetadataQuery.from({resource: 'codelist', detail: 'allstubs'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (references)', -> + expected = "http://test.com/codelist?references=datastructure" + query = MetadataQuery.from({ + resource: 'codelist' + references: 'datastructure' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds params when needed (detail & refs)', -> + expected = "http://test.com/codelist?detail=allstubs&references=datastructure" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'allstubs' + references: 'datastructure' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports referencepartial since v1.3.0', -> + expected = "http://test.com/codelist?detail=referencepartial" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'referencepartial' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports allcompletestubs since v1.3.0', -> + expected = "http://test.com/codelist?detail=allcompletestubs" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'allcompletestubs' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports referencecompletestubs since v1.3.0', -> + expected = "http://test.com/codelist?detail=referencecompletestubs" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'referencecompletestubs' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports raw since 2.0.0', -> + expected = "http://test.com/structure/codelist/*/*/~/*?\ + detail=raw&references=none" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'raw' + }) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/codelist?detail=raw" + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'raw' + }) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support referencepartial before v1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'referencepartial' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'referencepartial not allowed in v1.1.0') + + it 'does not support allcompletestubs before v1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'allcompletestubs' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'allcompletestubs not allowed in v1.2.0') + + it 'does not support referencecompletestubs before v1.3.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'referencecompletestubs' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'referencecompletestubs not allowed in v1.0.2') + + it 'does not support raw before v2.0.0', -> + query = MetadataQuery.from({ + resource: 'codelist' + detail: 'raw' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'raw not allowed in v1.5.0') + + it 'supports actualconstraint since v1.3.0 and until v2.0.0', -> + expected = 'http://test.com/actualconstraint' + query = MetadataQuery.from({resource: 'actualconstraint'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports allowedconstraint since v1.3.0 and until v2.0.0', -> + expected = 'http://test.com/allowedconstraint' + query = MetadataQuery.from({resource: 'allowedconstraint'}) + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'allowedconstraint not allowed in v2.0.0') + + it 'does not support actualconstraint before v1.3.0', -> + query = MetadataQuery.from({resource: 'actualconstraint'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'actualconstraint not allowed in v1.2.0') + + it 'does not support allowedconstraint before v1.3.0', -> + query = MetadataQuery.from({resource: 'allowedconstraint'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'allowedconstraint not allowed in v1.0.2') + + it 'supports actualconstraint since v1.3.0 and until v2.0.0', -> + expected = 'http://test.com/actualconstraint' + query = MetadataQuery.from({resource: 'actualconstraint'}) + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'actualconstraint not allowed in v2.0.0') + + it 'supports VTL artefacts since v1.5.0 (type)', -> + expected = 'http://test.com/transformationscheme' + query = MetadataQuery.from({resource: 'transformationscheme'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support VTL artefacts before v1.5.0 (type)', -> + query = MetadataQuery.from({resource: 'transformationscheme'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'transformationscheme not allowed in v1.2.0') + + it 'supports VTL artefacts since v1.5.0 (references)', -> + expected = 'http://test.com/codelist?references=transformationscheme' + query = MetadataQuery.from({resource: 'codelist', references: 'transformationscheme'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support VTL artefacts before v1.5.0 (references)', -> + query = MetadataQuery.from({resource: 'codelist', references: 'transformationscheme'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'transformationscheme not allowed as reference in v1.2.0') + + it 'supports ancestors since v2.0.0 (references)', -> + expected = 'http://test.com/structure/codelist?references=ancestors' + query = MetadataQuery.from({resource: 'codelist', references: 'ancestors'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support ancestors before v2.0.0 (references)', -> + query = MetadataQuery.from({resource: 'codelist', references: 'ancestors'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'ancestors not allowed as reference in v1.5.0') + + it 'supports semver since v2.0.0 (version)', -> + expected = 'http://test.com/structure/codelist/BIS/CL_FREQ/1.2+.0/*?\ + detail=full&references=none' + query = MetadataQuery.from( + {resource: 'codelist', agency: 'BIS', id: 'CL_FREQ', version: '1.2+.0'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = 'http://test.com/structure/codelist/BIS/CL_FREQ/1.2+.0' + query = MetadataQuery.from( + {resource: 'codelist', agency: 'BIS', id: 'CL_FREQ', version: '1.2+.0'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support semver before v2.0.0', -> + query = MetadataQuery.from( + {resource: 'dataflow', id: 'EXR', agency: 'ECB', version: '~'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') + + query = MetadataQuery.from( + {resource: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.2+.42'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') + + it 'rewrites version keywords since v2.0.0', -> + expected = "http://test.com/structure/dataflow/BIS/EXR/~\ + ?detail=full&references=none" + query = MetadataQuery.from( + {resource: 'dataflow', agency: 'BIS', id: 'EXR', version: 'latest'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + expected = "http://test.com/structure/dataflow/BIS/EXR" + query = MetadataQuery.from( + {resource: 'dataflow', agency: 'BIS', id: 'EXR', version: 'latest'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected diff --git a/test/utils/url-generator-schema.test.coffee b/test/utils/url-generator-schema.test.coffee new file mode 100644 index 0000000..d0d7248 --- /dev/null +++ b/test/utils/url-generator-schema.test.coffee @@ -0,0 +1,127 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{DataQuery} = require '../../src/data/data-query' +{MetadataQuery} = require '../../src/metadata/metadata-query' +{AvailabilityQuery} = require '../../src/avail/availability-query' +{SchemaQuery} = require '../../src/schema/schema-query' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for schema queries', -> + + it 'generates a URL for a schema query', -> + expected = "http://sdw-wsrest.ecb.europa.eu/service/schema/dataflow\ + /ECB/EXR/1.0?explicitMeasure=false" + query = SchemaQuery.from({context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.0'}) + service = Service.ECB + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a schema query (with dimensionAtObservation)', -> + expected = "http://sdw-wsrest.ecb.europa.eu/service/schema/dataflow\ + /ECB/EXR/latest?explicitMeasure=false&dimensionAtObservation=TEST" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', obsDimension: 'TEST'}) + service = Service.ECB + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'offers to skip default values for schema', -> + expected = "http://test.com/schema/dataflow/ECB/EXR" + query = SchemaQuery.from({context: 'dataflow', id: 'EXR', agency: 'ECB'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (version)', -> + expected = "http://test.com/schema/dataflow/ECB/EXR/1.1" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.1'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (explicit)', -> + expected = "http://test.com/schema/dataflow/ECB/EXR?explicitMeasure=true" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', explicit: true}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (obsDimension)', -> + expected = + "http://test.com/schema/dataflow/ECB/EXR?dimensionAtObservation=TEST" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', obsDimension: 'TEST'}) + service = Service.from({url: 'http://test.com'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip defaults but adds them when needed (query params)', -> + expected = "http://test.com/schema/dataflow/ECB/EXR?explicitMeasure=true\ + &dimensionAtObservation=TEST" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', explicit: true, + obsDimension: 'TEST'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'supports metadataprovisionagreement since v2.0.0', -> + expected = "http://test.com/schema/metadataprovisionagreement/ECB/EXR?\ + dimensionAtObservation=TEST" + query = SchemaQuery.from( + {context: 'metadataprovisionagreement', id: 'EXR', agency: 'ECB', + obsDimension: 'TEST'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'does not support metadataprovisionagreement before v2.0.0', -> + query = SchemaQuery.from( + {context: 'metadataprovisionagreement', id: 'EXR', agency: 'ECB', + obsDimension: 'TEST'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'metadataprovisionagreement not allowed in v1.5.0') + + it 'does not support explicitMeasure starting with v2.0.0', -> + query = SchemaQuery.from( + {context: 'provisionagreement', id: 'EXR', agency: 'ECB', + obsDimension: 'TEST', explicit: true}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'explicit parameter not allowed in v2.0.0') + + it 'does no longer use default explicitMeasure starting with v2.0.0', -> + expected = "http://test.com/schema/dataflow/ECB/EXR/1.0.0" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.0.0'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'does not support semver before v2.0.0', -> + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '~'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') + + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.2+.42'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') + + it 'rewrites version keywords since v2.0.0', -> + expected = "http://test.com/schema/dataflow/ECB/EXR/~?\ + dimensionAtObservation=TEST" + query = SchemaQuery.from( + {context: 'dataflow', id: 'EXR', agency: 'ECB', version: 'latest', + obsDimension: 'TEST'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected diff --git a/test/utils/url-generator.test.coffee b/test/utils/url-generator.test.coffee index f921535..b04c294 100644 --- a/test/utils/url-generator.test.coffee +++ b/test/utils/url-generator.test.coffee @@ -8,1285 +8,7 @@ should = require('chai').should() {SchemaQuery} = require '../../src/schema/schema-query' {UrlGenerator} = require '../../src/utils/url-generator' -describe 'URL Generator', -> - - describe 'for metadata queries', -> - - it 'generates a URL for a metadata query', -> - expected = "http://sdw-wsrest.ecb.europa.eu/service/codelist/ECB/CL_FREQ/\ - latest?detail=full&references=none" - query = - MetadataQuery.from({resource: 'codelist', id: 'CL_FREQ', agency: 'ECB'}) - service = Service.ECB - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a metadata ItemScheme query', -> - expected = "http://test.com/service/codelist/all/all/\ - latest/all?detail=full&references=none" - query = MetadataQuery.from({resource: 'codelist'}) - service = Service.from( - {url: "http://test.com/service/", api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a metadata non-ItemScheme query', -> - expected = "http://test.com/service/dataflow/all/all/\ - latest?detail=full&references=none" - query = MetadataQuery.from({resource: 'dataflow'}) - service = Service.from( - {url: "http://test.com/service/", api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'supports item queries for API version 1.1.0 and above', -> - expected = "http://test.com/codelist/ECB/CL_FREQ/latest/A\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - item: 'A' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support item queries before API version 1.1.0', -> - expected = "http://test.com/codelist/ECB/CL_FREQ/latest\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - item: 'A' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'treats hierarchical codelists as item schemes for API version 1.2.0', -> - expected = "http://test.com/hierarchicalcodelist/BIS/HCL/latest/HIERARCHY\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'hierarchicalcodelist' - id: 'HCL' - agency: 'BIS' - item: 'HIERARCHY' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'hierarchicalcodelist not allowed in v2.0.0') - - it 'does not support hiearchy queries before API version 1.2.0', -> - expected = "http://test.com/hierarchicalcodelist/BIS/HCL/latest\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'hierarchicalcodelist' - id: 'HCL' - agency: 'BIS' - item: 'HIERARCHY' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'Does not support multiple artefact types before API version 2.0.0', -> - query = MetadataQuery.from({resource: 'codelist+dataflow'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'codelist+dataflow not allowed in v1.5.0') - - it 'Supports multiple artefact types since API 2.0.0', -> - expected = "http://test.com/structure/codelist,dataflow/*/*/~\ - ?detail=full&references=none" - query = MetadataQuery.from({resource: 'codelist,dataflow'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist,dataflow" - query = MetadataQuery.from({resource: 'codelist,dataflow'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites + for multiple artefact types in API 2.0.0', -> - expected = "http://test.com/structure/codelist,dataflow/*/*/~\ - ?detail=full&references=none" - query = MetadataQuery.from({resource: 'codelist+dataflow'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist,dataflow" - query = MetadataQuery.from({resource: 'codelist+dataflow'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Supports all for artefact types via * since API 2.0.0', -> - expected = "http://test.com/structure/*/*/*/~\ - ?detail=full&references=none" - query = MetadataQuery.from({resource: '*'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/*" - query = MetadataQuery.from({resource: '*'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports multiple agencies for API version 1.3.0 and above', -> - expected = "http://test.com/codelist/ECB+BIS/CL_FREQ/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB+BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ/~/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB,BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - - it 'does not support multiple agencies before API version 1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB+BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Multiple agencies not allowed in v1.2.0') - - it 'Rewrites , for multiple agencies before API version 2.0.0', -> - expected = "http://test.com/codelist/ECB+BIS/CL_FREQ/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB,BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'Rewrites + for multiple agencies since API 2.0.0', -> - expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ/~/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB+BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/ECB,BIS/CL_FREQ" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB+BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites * for agencies before API version 2.0.0', -> - expected = "http://test.com/codelist/all/CL_FREQ/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: '*' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/codelist/all/CL_FREQ" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: '*' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites all for agencies since API 2.0.0', -> - expected = "http://test.com/structure/codelist/*/CL_FREQ/~/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'all' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/*/CL_FREQ" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'all' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports multiple IDs for API version 1.3.0 and above', -> - expected = "http://test.com/codelist/ECB/CL_FREQ+CL_DECIMALS/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ+CL_DECIMALS' - agency: 'ECB' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support multiple IDs before API version 1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ+CL_DECIMALS' - agency: 'ECB' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Multiple IDs not allowed in v1.2.0') - - it 'Rewrites , for multiple resource IDs before API version 2.0.0', -> - expected = "http://test.com/codelist/BIS/CL_FREQ+CL_DECIMALS/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ,CL_DECIMALS' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/codelist/BIS/CL_FREQ+CL_DECIMALS" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ,CL_DECIMALS' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites + for multiple resource IDs since API 2.0.0', -> - expected = "http://test.com/structure/codelist/BIS/CL_FREQ,CL_UNIT/~/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ+CL_UNIT' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/BIS/CL_FREQ,CL_UNIT" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ+CL_UNIT' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites * for resource IDs before API version 2.0.0', -> - expected = "http://test.com/codelist/BIS/all/latest/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: '*' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/codelist/BIS/all/1.0" - query = MetadataQuery.from({ - resource: 'codelist' - id: '*' - agency: 'BIS' - version: '1.0' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites all for resources IDs since API 2.0.0', -> - expected = "http://test.com/structure/codelist/BIS/*/~/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'all' - agency: 'BIS' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/BIS/*/1.0" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'all' - agency: 'BIS' - version: '1.0' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports multiple versions for API version 1.3.0 and above', -> - expected = "http://test.com/codelist/ECB/CL_FREQ/1.0+1.1/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - version: '1.0+1.1' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/ECB/CL_FREQ/1.0.0,1.1.0/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - version: '1.0.0,1.1.0' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support multiple versions before API version 1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - version: '1.0+1.1' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Multiple versions not allowed in v1.1.0') - - it 'supports multiple items for API version 1.3.0 and above', -> - expected = "http://test.com/codelist/ECB/CL_FREQ/1.0/A+M\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - version: '1.0' - item: 'A+M' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support multiple items before API version 1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'ECB' - version: '1.0' - item: 'A+M' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Multiple items not allowed in v1.2.0') - - it 'Rewrites + for multiple items since API 2.0.0', -> - expected = "http://test.com/structure/codelist/BIS/CL_FREQ/~/A,M\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - item: 'A+M' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/BIS/CL_FREQ/~/A,M" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - item: 'A+M' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites * for items before API version 2.0.0', -> - expected = "http://test.com/codelist/BIS/CL_FREQ/1.0/all\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - version: '1.0' - item: '*' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/codelist/BIS/CL_FREQ/1.0" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - version: '1.0' - item: '*' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'Rewrites all for items since API 2.0.0', -> - expected = "http://test.com/structure/codelist/BIS/CL_FREQ/1.0/*\ - ?detail=full&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - version: '1.0' - item: 'all' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist/BIS/CL_FREQ/1.0" - query = MetadataQuery.from({ - resource: 'codelist' - id: 'CL_FREQ' - agency: 'BIS' - version: '1.0' - item: 'all' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip default values for metadata', -> - expected = "http://test.com/codelist" - query = MetadataQuery.from({resource: 'codelist'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (id)', -> - expected = "http://test.com/codelist/all/CL_FREQ" - query = MetadataQuery.from({resource: 'codelist', id: 'CL_FREQ'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (version)', -> - expected = "http://test.com/codelist/all/all/42" - query = MetadataQuery.from({resource: 'codelist', version: '42'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (item)', -> - expected = "http://test.com/codelist/all/all/latest/1" - query = MetadataQuery.from({resource: 'codelist', item: '1'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (item, old API)', -> - expected = "http://test.com/codelist" - query = MetadataQuery.from({resource: 'codelist', item: '1'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (detail)', -> - expected = "http://test.com/codelist?detail=allstubs" - query = MetadataQuery.from({resource: 'codelist', detail: 'allstubs'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (references)', -> - expected = "http://test.com/codelist?references=datastructure" - query = MetadataQuery.from({ - resource: 'codelist' - references: 'datastructure' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (detail & refs)', -> - expected = "http://test.com/codelist?detail=allstubs&references=datastructure" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'allstubs' - references: 'datastructure' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports referencepartial since v1.3.0', -> - expected = "http://test.com/codelist?detail=referencepartial" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'referencepartial' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports allcompletestubs since v1.3.0', -> - expected = "http://test.com/codelist?detail=allcompletestubs" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'allcompletestubs' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports referencecompletestubs since v1.3.0', -> - expected = "http://test.com/codelist?detail=referencecompletestubs" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'referencecompletestubs' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports raw since 2.0.0', -> - expected = "http://test.com/structure/codelist/*/*/~/*?\ - detail=raw&references=none" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'raw' - }) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/codelist?detail=raw" - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'raw' - }) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support referencepartial before v1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'referencepartial' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'referencepartial not allowed in v1.1.0') - - it 'does not support allcompletestubs before v1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'allcompletestubs' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'allcompletestubs not allowed in v1.2.0') - - it 'does not support referencecompletestubs before v1.3.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'referencecompletestubs' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'referencecompletestubs not allowed in v1.0.2') - - it 'does not support raw before v2.0.0', -> - query = MetadataQuery.from({ - resource: 'codelist' - detail: 'raw' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'raw not allowed in v1.5.0') - - it 'supports actualconstraint since v1.3.0 and until v2.0.0', -> - expected = 'http://test.com/actualconstraint' - query = MetadataQuery.from({resource: 'actualconstraint'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports allowedconstraint since v1.3.0 and until v2.0.0', -> - expected = 'http://test.com/allowedconstraint' - query = MetadataQuery.from({resource: 'allowedconstraint'}) - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'allowedconstraint not allowed in v2.0.0') - - it 'does not support actualconstraint before v1.3.0', -> - query = MetadataQuery.from({resource: 'actualconstraint'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'actualconstraint not allowed in v1.2.0') - - it 'does not support allowedconstraint before v1.3.0', -> - query = MetadataQuery.from({resource: 'allowedconstraint'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'allowedconstraint not allowed in v1.0.2') - - it 'supports actualconstraint since v1.3.0 and until v2.0.0', -> - expected = 'http://test.com/actualconstraint' - query = MetadataQuery.from({resource: 'actualconstraint'}) - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_4_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'actualconstraint not allowed in v2.0.0') - - it 'supports VTL artefacts since v1.5.0 (type)', -> - expected = 'http://test.com/transformationscheme' - query = MetadataQuery.from({resource: 'transformationscheme'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support VTL artefacts before v1.5.0 (type)', -> - query = MetadataQuery.from({resource: 'transformationscheme'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'transformationscheme not allowed in v1.2.0') - - it 'supports VTL artefacts since v1.5.0 (references)', -> - expected = 'http://test.com/codelist?references=transformationscheme' - query = MetadataQuery.from({resource: 'codelist', references: 'transformationscheme'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support VTL artefacts before v1.5.0 (references)', -> - query = MetadataQuery.from({resource: 'codelist', references: 'transformationscheme'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'transformationscheme not allowed as reference in v1.2.0') - - it 'supports ancestors since v2.0.0 (references)', -> - expected = 'http://test.com/structure/codelist?references=ancestors' - query = MetadataQuery.from({resource: 'codelist', references: 'ancestors'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support ancestors before v2.0.0 (references)', -> - query = MetadataQuery.from({resource: 'codelist', references: 'ancestors'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'ancestors not allowed as reference in v1.5.0') - - it 'supports semver since v2.0.0 (version)', -> - expected = 'http://test.com/structure/codelist/BIS/CL_FREQ/1.2+.0/*?\ - detail=full&references=none' - query = MetadataQuery.from( - {resource: 'codelist', agency: 'BIS', id: 'CL_FREQ', version: '1.2+.0'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = 'http://test.com/structure/codelist/BIS/CL_FREQ/1.2+.0' - query = MetadataQuery.from( - {resource: 'codelist', agency: 'BIS', id: 'CL_FREQ', version: '1.2+.0'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support semver before v2.0.0', -> - query = MetadataQuery.from( - {resource: 'dataflow', id: 'EXR', agency: 'ECB', version: '~'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') - - query = MetadataQuery.from( - {resource: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.2+.42'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') - - it 'rewrites version keywords since v2.0.0', -> - expected = "http://test.com/structure/dataflow/BIS/EXR/~\ - ?detail=full&references=none" - query = MetadataQuery.from( - {resource: 'dataflow', agency: 'BIS', id: 'EXR', version: 'latest'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/structure/dataflow/BIS/EXR" - query = MetadataQuery.from( - {resource: 'dataflow', agency: 'BIS', id: 'EXR', version: 'latest'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - describe 'for data queries', -> - - it 'generates a URL for a full data query', -> - expected = "http://test.com/data/EXR/A..EUR.SP00.A/ECB?\ - dimensionAtObservation=CURRENCY&detail=nodata&includeHistory=true\ - &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z\ - &firstNObservations=1&lastNObservations=1" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - provider: 'ECB' - obsDimension: 'CURRENCY' - detail: 'nodata' - history: true - start: '2010' - end: '2015' - updatedAfter: '2016-03-01T00:00:00Z' - firstNObs: 1 - lastNObs: 1 - }) - service = Service.from({ - url: 'http://test.com' - api: ApiVersion.v1_1_0 - }) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a full data query (2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A\ - ?dimensionAtObservation=CURRENCY\ - &attributes=dataset,series&measures=none\ - &includeHistory=true\ - &updatedAfter=2016-03-01T00:00:00Z\ - &firstNObservations=1&lastNObservations=1" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - obsDimension: 'CURRENCY' - detail: 'nodata' - history: true - updatedAfter: '2016-03-01T00:00:00Z' - firstNObs: 1 - lastNObs: 1 - }) - service = Service.from({ - url: 'http://test.com' - api: ApiVersion.v2_0_0 - }) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a partial data query', -> - expected = "http://test.com/data/EXR/A..EUR.SP00.A/all?\ - detail=full&includeHistory=false" - query = DataQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_1_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a partial data query (2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ - attributes=dsd&measures=all&includeHistory=false" - query = DataQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'supports history but only for API version 1.1.0 and above', -> - expected = "http://test.com/data/EXR/A..EUR.SP00.A/ECB?\ - dimensionAtObservation=CURRENCY&detail=nodata\ - &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z\ - &firstNObservations=1&lastNObservations=1" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - provider: 'ECB' - obsDimension: 'CURRENCY' - detail: 'nodata' - history: true - start: '2010' - end: '2015' - updatedAfter: '2016-03-01T00:00:00Z' - firstNObs: 1 - lastNObs: 1 - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_0_2}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'defaults to latest API', -> - expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ - dimensionAtObservation=CURRENCY&attributes=dataset,series&measures=none\ - &includeHistory=true&updatedAfter=2016-03-01T00:00:00Z\ - &firstNObservations=1&lastNObservations=1" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - obsDimension: 'CURRENCY' - detail: 'nodata' - history: true - updatedAfter: '2016-03-01T00:00:00Z' - firstNObs: 1 - lastNObs: 1 - }) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'offers to skip default values for data', -> - expected = "http://test.com/data/EXR" - query = DataQuery.from({flow: 'EXR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip default values for data (v2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR" - query = DataQuery.from({flow: 'EXR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (provider)', -> - expected = "http://test.com/data/EXR/all/ECB" - query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (start)', -> - expected = "http://test.com/data/EXR?startPeriod=2010" - query = DataQuery.from({flow: 'EXR', start: '2010'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (end)', -> - expected = "http://test.com/data/EXR?endPeriod=2010" - query = DataQuery.from({flow: 'EXR', end: '2010'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (updatedAfter)', -> - expected = "http://test.com/data/EXR?updatedAfter=2016-03-01T00:00:00Z" - query = DataQuery.from({ - flow: 'EXR' - updatedAfter: '2016-03-01T00:00:00Z' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (updatedAfter, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?updatedAfter=2016-03-01T00:00:00Z" - query = DataQuery.from({ - flow: 'EXR' - updatedAfter: '2016-03-01T00:00:00Z' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (firstNObs)', -> - expected = "http://test.com/data/EXR?firstNObservations=1" - query = DataQuery.from({flow: 'EXR', firstNObs: 1}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (firstNObs, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?firstNObservations=1" - query = DataQuery.from({flow: 'EXR', firstNObs: 1}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (lastNObs)', -> - expected = "http://test.com/data/EXR?lastNObservations=2" - query = DataQuery.from({flow: 'EXR', lastNObs: 2}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (lastNObs, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?lastNObservations=2" - query = DataQuery.from({flow: 'EXR', lastNObs: 2}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (detail)', -> - expected = "http://test.com/data/EXR?detail=dataonly" - query = DataQuery.from({flow: 'EXR', detail: 'dataonly'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (detail, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?attributes=none&measures=all" - query = DataQuery.from({flow: 'EXR', detail: 'dataonly'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (history)', -> - expected = "http://test.com/data/EXR?includeHistory=true" - query = DataQuery.from({flow: 'EXR', history: true}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (history, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?includeHistory=true" - query = DataQuery.from({flow: 'EXR', history: true}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (obsDim)', -> - expected = "http://test.com/data/EXR?dimensionAtObservation=CURR" - query = DataQuery.from({flow: 'EXR', obsDimension: 'CURR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (obsDim, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR?dimensionAtObservation=CURR" - query = DataQuery.from({flow: 'EXR', obsDimension: 'CURR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (various)', -> - expected = "http://test.com/data/EXR/A..EUR.SP00.A?\ - updatedAfter=2016-03-01T00:00:00Z\ - &startPeriod=2010&dimensionAtObservation=CURRENCY" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - obsDimension: 'CURRENCY' - start: '2010' - updatedAfter: '2016-03-01T00:00:00Z' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds params when needed (various, 2.0.0)', -> - expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ - updatedAfter=2016-03-01T00:00:00Z\ - &dimensionAtObservation=CURRENCY" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - obsDimension: 'CURRENCY' - updatedAfter: '2016-03-01T00:00:00Z' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports multiple providers for API version 1.3.0 and until 2.0.0', -> - expected = "http://test.com/data/EXR/A..EUR.SP00.A/SDMX,ECB+BIS?\ - updatedAfter=2016-03-01T00:00:00Z\ - &startPeriod=2010&dimensionAtObservation=CURRENCY" - query = DataQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - obsDimension: 'CURRENCY' - start: '2010' - updatedAfter: '2016-03-01T00:00:00Z' - provider: ['SDMX,ECB', 'BIS'] - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_3_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support providers before API version 1.3.0', -> - query = DataQuery.from({flow: 'EXR', provider: 'SDMX,ECB+BIS'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_2_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Multiple providers not allowed in v1.2.0') - - it 'throws an error when using provider with 2.0.0', -> - query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'provider not allowed in v2.0.0') - - query = DataQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'provider not allowed in v2.0.0') - - it 'throws an error when using start with 2.0.0', -> - query = DataQuery.from({flow: 'EXR', start: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'start not allowed in v2.0.0') - - query = DataQuery.from({flow: 'EXR', start: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'start not allowed in v2.0.0') - - it 'throws an error when using end with 2.0.0', -> - query = DataQuery.from({flow: 'EXR', end: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'end not allowed in v2.0.0') - - query = DataQuery.from({flow: 'EXR', end: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'end not allowed in v2.0.0') - - it 'translates details=full with 2.0.0', -> - expected = "http://test.com/data/dataflow/*/EXR/*/*?\ - attributes=dsd&measures=all&includeHistory=false" - query = DataQuery.from({ - flow: 'EXR' - detail: 'full' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/*/EXR" - query = DataQuery.from({ - flow: 'EXR' - detail: 'full' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates details=dataonly with 2.0.0', -> - expected = "http://test.com/data/dataflow/*/EXR/*/*?\ - attributes=none&measures=all&includeHistory=false" - query = DataQuery.from({ - flow: 'EXR' - detail: 'dataonly' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/*/EXR?\ - attributes=none&measures=all" - query = DataQuery.from({ - flow: 'EXR' - detail: 'dataonly' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates details=serieskeysonly with 2.0.0', -> - expected = "http://test.com/data/dataflow/*/EXR/*/*?\ - attributes=none&measures=none&includeHistory=false" - query = DataQuery.from({ - flow: 'EXR' - detail: 'serieskeysonly' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/*/EXR?\ - attributes=none&measures=none" - query = DataQuery.from({ - flow: 'EXR' - detail: 'serieskeysonly' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates details=nodata with 2.0.0', -> - expected = "http://test.com/data/dataflow/*/EXR/*/*?\ - attributes=dataset,series&measures=none&includeHistory=false" - query = DataQuery.from({ - flow: 'EXR' - detail: 'nodata' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/*/EXR?\ - attributes=dataset,series&measures=none" - query = DataQuery.from({ - flow: 'EXR' - detail: 'nodata' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates 1-part dataflow in the correct 2.0.0 context', -> - expected = "http://test.com/data/dataflow/*/EXR/*/*?\ - attributes=dsd&measures=all&includeHistory=false" - query = DataQuery.from({ - flow: 'EXR' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/*/EXR" - query = DataQuery.from({ - flow: 'EXR' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates 2-parts dataflow in the correct 2.0.0 context', -> - expected = "http://test.com/data/dataflow/ECB/EXR/*/*?\ - attributes=dsd&measures=all&includeHistory=false" - query = DataQuery.from({ - flow: 'ECB,EXR' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/ECB/EXR" - query = DataQuery.from({ - flow: 'ECB,EXR' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'translates 3-parts dataflow in the correct 2.0.0 context', -> - expected = "http://test.com/data/dataflow/ECB/EXR/1.42/*?\ - attributes=dsd&measures=all&includeHistory=false" - query = DataQuery.from({ - flow: 'ECB,EXR,1.42' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - expected = "http://test.com/data/dataflow/ECB/EXR/1.42" - query = DataQuery.from({ - flow: 'ECB,EXR,1.42' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'rejects keys containing dimensions separated with + (2.0.0)', -> - query = DataQuery.from({ - flow: 'ECB,EXR,1.42' - key: 'A+M.CHF' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, '+ not allowed in key in v2.0.0') - - query = DataQuery.from({ - flow: 'ECB,EXR,1.42' - key: 'A+M.CHF' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service, true) - should.Throw(test, Error, '+ not allowed in key in v2.0.0') +describe 'URL Generator (generic)', -> it 'throws an exception if no query is supplied', -> test = -> new UrlGenerator().getUrl() @@ -1317,372 +39,3 @@ describe 'URL Generator', -> }) test = -> new UrlGenerator().getUrl query, {id: 'test'} should.Throw(test, ReferenceError, 'not a valid service') - -describe 'for availability queries', -> - - it 'generates a URL for a full availability query', -> - expected = 'http://test.com/availableconstraint/EXR/A..EUR.SP00.A/ECB/FREQ?\ - mode=available&references=none\ - &startPeriod=2010&endPeriod=2015&updatedAfter=2016-03-01T00:00:00Z' - query = AvailabilityQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - provider: 'ECB' - component: 'FREQ' - start: '2010' - end: '2015' - updatedAfter: '2016-03-01T00:00:00Z' - mode: 'available' - references: 'none' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a full availability query (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ - mode=available&references=none\ - &updatedAfter=2016-03-01T00:00:00Z' - query = AvailabilityQuery.from({ - flow: 'EXR' - key: 'A..EUR.SP00.A' - component: 'FREQ' - updatedAfter: '2016-03-01T00:00:00Z' - mode: 'available' - references: 'none' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a partial availability query', -> - expected = 'http://test.com/availableconstraint/EXR/A..EUR.SP00.A/all/all?\ - mode=exact&references=none' - query = AvailabilityQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a partial availability query (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/*?\ - mode=exact&references=none' - query = AvailabilityQuery.from({flow: 'EXR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'supports minimal query if proper query class is used', -> - expected = 'http://test.com/availableconstraint/EXR/all/all/all?\ - mode=exact&references=none' - query = AvailabilityQuery.from({flow: 'EXR'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support availability queries before v1.3.0', -> - query = AvailabilityQuery.from({flow: 'EXR', key: 'A..EUR.SP00.A'}) - service = Service.from({url: 'http://test.com', api: 'v1.2.0'}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Availability query not supported in v1.2.0') - - it 'offers to skip default values for availability', -> - expected = 'http://test.com/availableconstraint/EXR' - query = AvailabilityQuery.from({ - flow: 'EXR' - mode: 'exact' - references: 'none' - }) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip default values for availability (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR' - query = AvailabilityQuery.from({ - flow: 'EXR' - mode: 'exact' - references: 'none' - }) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (key, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A.CH' - query = AvailabilityQuery.from({flow: 'EXR', key: 'A.CH'}) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (provider)', -> - expected = 'http://test.com/availableconstraint/EXR/all/ECB' - query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (component)', -> - expected = 'http://test.com/availableconstraint/EXR/all/all/FREQ' - query = AvailabilityQuery.from({flow: 'EXR', component: 'FREQ'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (component, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/FREQ' - query = AvailabilityQuery.from({flow: 'EXR', component: 'FREQ'}) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (mode)', -> - expected = 'http://test.com/availableconstraint/EXR?mode=available' - query = AvailabilityQuery.from({flow: 'EXR', mode: 'available'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (mode, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ - ?mode=available' - query = AvailabilityQuery.from({flow: 'EXR', mode: 'available'}) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (refs)', -> - expected = 'http://test.com/availableconstraint/EXR?references=codelist' - query = AvailabilityQuery.from({flow: 'EXR', references: 'codelist'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (refs, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ - ?references=codelist' - query = AvailabilityQuery.from({flow: 'EXR', references: 'codelist'}) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (start)', -> - expected = 'http://test.com/availableconstraint/EXR?startPeriod=2007' - query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (end)', -> - expected = 'http://test.com/availableconstraint/EXR?endPeriod=2073' - query = AvailabilityQuery.from({flow: 'EXR', end: '2073'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (start & end)', -> - expected = 'http://test.com/availableconstraint/EXR?startPeriod=2007&\ - endPeriod=2073' - query = AvailabilityQuery.from({flow: 'EXR', start: '2007', end: '2073'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (upd)', -> - expected = 'http://test.com/availableconstraint/EXR?\ - updatedAfter=2016-03-01T00:00:00Z' - query = AvailabilityQuery.from({ - flow: 'EXR' - updatedAfter: '2016-03-01T00:00:00Z'}) - service = Service.from({url: 'http://test.com', api: 'v1.5.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (upd, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ - updatedAfter=2016-03-01T00:00:00Z' - query = AvailabilityQuery.from({ - flow: 'EXR' - updatedAfter: '2016-03-01T00:00:00Z'}) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (multi, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ - mode=available&updatedAfter=2016-03-01T00:00:00Z' - query = AvailabilityQuery.from({ - flow: 'EXR' - updatedAfter: '2016-03-01T00:00:00Z', - mode: 'available' - }) - service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'throws an error when using provider with 2.0.0', -> - query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'provider not allowed in v2.0.0') - - query = AvailabilityQuery.from({flow: 'EXR', provider: 'ECB'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'provider not allowed in v2.0.0') - - it 'throws an error when using start with 2.0.0', -> - query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'start not allowed in v2.0.0') - - query = AvailabilityQuery.from({flow: 'EXR', start: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'start not allowed in v2.0.0') - - it 'throws an error when using end with 2.0.0', -> - query = AvailabilityQuery.from({flow: 'EXR', end: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service - should.Throw(test, Error, 'end not allowed in v2.0.0') - - query = AvailabilityQuery.from({flow: 'EXR', end: '2007'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl query, service, true - should.Throw(test, Error, 'end not allowed in v2.0.0') - - it 'rejects keys containing dimensions separated with + (2.0.0)', -> - query = AvailabilityQuery.from({ - flow: 'ECB,EXR,1.42' - key: 'A+M.CHF' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, '+ not allowed in key in v2.0.0') - - query = AvailabilityQuery.from({ - flow: 'ECB,EXR,1.42' - key: 'A+M.CHF' - }) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service, true) - should.Throw(test, Error, '+ not allowed in key in v2.0.0') - -describe 'for schema queries', -> - - it 'generates a URL for a schema query', -> - expected = "http://sdw-wsrest.ecb.europa.eu/service/schema/dataflow\ - /ECB/EXR/1.0?explicitMeasure=false" - query = SchemaQuery.from({context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.0'}) - service = Service.ECB - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'generates a URL for a schema query (with dimensionAtObservation)', -> - expected = "http://sdw-wsrest.ecb.europa.eu/service/schema/dataflow\ - /ECB/EXR/latest?explicitMeasure=false&dimensionAtObservation=TEST" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', obsDimension: 'TEST'}) - service = Service.ECB - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'offers to skip default values for schema', -> - expected = "http://test.com/schema/dataflow/ECB/EXR" - query = SchemaQuery.from({context: 'dataflow', id: 'EXR', agency: 'ECB'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (version)', -> - expected = "http://test.com/schema/dataflow/ECB/EXR/1.1" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.1'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (explicit)', -> - expected = "http://test.com/schema/dataflow/ECB/EXR?explicitMeasure=true" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', explicit: true}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (obsDimension)', -> - expected = - "http://test.com/schema/dataflow/ECB/EXR?dimensionAtObservation=TEST" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', obsDimension: 'TEST'}) - service = Service.from({url: 'http://test.com'}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'offers to skip defaults but adds them when needed (query params)', -> - expected = "http://test.com/schema/dataflow/ECB/EXR?explicitMeasure=true\ - &dimensionAtObservation=TEST" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', explicit: true, - obsDimension: 'TEST'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'supports metadataprovisionagreement since v2.0.0', -> - expected = "http://test.com/schema/metadataprovisionagreement/ECB/EXR?\ - dimensionAtObservation=TEST" - query = SchemaQuery.from( - {context: 'metadataprovisionagreement', id: 'EXR', agency: 'ECB', - obsDimension: 'TEST'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service, true) - url.should.equal expected - - it 'does not support metadataprovisionagreement before v2.0.0', -> - query = SchemaQuery.from( - {context: 'metadataprovisionagreement', id: 'EXR', agency: 'ECB', - obsDimension: 'TEST'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'metadataprovisionagreement not allowed in v1.5.0') - - it 'does not support explicitMeasure starting with v2.0.0', -> - query = SchemaQuery.from( - {context: 'provisionagreement', id: 'EXR', agency: 'ECB', - obsDimension: 'TEST', explicit: true}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'explicit parameter not allowed in v2.0.0') - - it 'does no longer use default explicitMeasure starting with v2.0.0', -> - expected = "http://test.com/schema/dataflow/ECB/EXR/1.0.0" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.0.0'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected - - it 'does not support semver before v2.0.0', -> - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '~'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') - - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', version: '1.2+.42'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) - test = -> new UrlGenerator().getUrl(query, service) - should.Throw(test, Error, 'Semantic versioning not allowed in v1.5.0') - - it 'rewrites version keywords since v2.0.0', -> - expected = "http://test.com/schema/dataflow/ECB/EXR/~?\ - dimensionAtObservation=TEST" - query = SchemaQuery.from( - {context: 'dataflow', id: 'EXR', agency: 'ECB', version: 'latest', - obsDimension: 'TEST'}) - service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) - url = new UrlGenerator().getUrl(query, service) - url.should.equal expected From df6a958bc6d8ca58cbfe135f5b07f9904644407b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 11:45:30 +0200 Subject: [PATCH 02/47] Add option to translate version to key --- src/utils/api-version.coffee | 16 ++++++++++++++++ test/utils/api-version.test.coffee | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/utils/api-version.coffee b/src/utils/api-version.coffee index 0532713..1fccb14 100644 --- a/src/utils/api-version.coffee +++ b/src/utils/api-version.coffee @@ -157,5 +157,21 @@ resources = # The set of valid resources for the latest API version. LATEST: resourcesV6 +numbers = + v1_0_0: 0 + v1_0_1: 1 + v1_0_2: 2 + v1_1_0: 3 + v1_2_0: 4 + v1_3_0: 5 + v1_4_0: 6 + v1_5_0: 7 + v2_0_0: 8 + +getKeyFromVersion = (v) -> + v.replace /\./g, "_" + exports.ApiVersion = Object.freeze versions exports.ApiResources = Object.freeze resources +exports.ApiNumber = Object.freeze numbers +exports.getKeyFromVersion = getKeyFromVersion diff --git a/test/utils/api-version.test.coffee b/test/utils/api-version.test.coffee index d749f77..f289616 100644 --- a/test/utils/api-version.test.coffee +++ b/test/utils/api-version.test.coffee @@ -2,6 +2,8 @@ should = require('chai').should() {ApiVersion} = require '../../src/utils/api-version' {ApiResources} = require '../../src/utils/api-version' +{getKeyFromVersion} = require '../../src/utils/api-version' + describe 'API versions', -> @@ -196,3 +198,12 @@ describe 'API resources', -> it 'contains all the expected resources for the latest version and only those', -> ApiResources.LATEST.should.eql expectedResourcesV6 + +describe 'Translator', -> + + it 'translates version numbers into keys', -> + api = ApiVersion.v2_0_0 + k = getKeyFromVersion(api) + a = ApiVersion[k] + console.log(k) + api.should.equal a From d14f5816d369956f9e7f86b85a382054ea32694b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 11:55:57 +0200 Subject: [PATCH 03/47] Move generator for availability queries --- src/utils/url-generator-availability.coffee | 99 +++++++++++++++++++++ src/utils/url-generator-common.coffee | 28 ++++++ src/utils/url-generator.coffee | 95 +------------------- 3 files changed, 129 insertions(+), 93 deletions(-) create mode 100644 src/utils/url-generator-availability.coffee create mode 100644 src/utils/url-generator-common.coffee diff --git a/src/utils/url-generator-availability.coffee b/src/utils/url-generator-availability.coffee new file mode 100644 index 0000000..70dad8f --- /dev/null +++ b/src/utils/url-generator-availability.coffee @@ -0,0 +1,99 @@ +{ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' +{createEntryPoint, validateDataForV2, parseFlow} = require '../utils/url-generator-common' + +handleAvailabilityPathParams = (q) -> + path = [] + path.push q.component unless q.component is 'all' + path.push q.provider if q.provider isnt 'all' or path.length + path.push q.key if q.key isnt 'all' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +handleAvailabilityQueryParams = (q) -> + p = [] + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "endPeriod=#{q.end}" if q.end + p.push "startPeriod=#{q.start}" if q.start + p.push "mode=#{q.mode}" unless q.mode is 'exact' + p.push "references=#{q.references}" unless q.references is 'none' + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +handleAvailabilityV2PathParams = (q) -> + path = [] + path.push q.component unless q.component is 'all' + k = if q.key is 'all' then '*' else q.key + path.push k if k isnt '*' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +handleAvailabilityV2QueryParams = (q) -> + p = [] + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "mode=#{q.mode}" unless q.mode is 'exact' + p.push "references=#{q.references}" unless q.references is 'none' + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +createShortV1AvailUrl = (q, s) -> + u = createEntryPoint s + u += "availableconstraint/#{q.flow}" + u += handleAvailabilityPathParams(q) + u += handleAvailabilityQueryParams(q) + u + +createShortV2AvailUrl = (q, s) -> + validateDataForV2 q, s + u = createEntryPoint s + u += "availableconstraint/dataflow/" + fc = parseFlow q.flow + u += "#{fc[0]}/#{fc[1]}" + pp = handleAvailabilityV2PathParams(q) + if fc[2] isnt "*" or pp isnt "" + u += "/#{fc[2]}" + u += pp + u += handleAvailabilityV2QueryParams(q) + u + +createV1AvailUrl = (q, s) -> + url = createEntryPoint s + url += 'availableconstraint' + url += "/#{q.flow}/#{q.key}/#{q.provider}/#{q.component}" + url += "?mode=#{q.mode}&references=#{q.references}" + url += "&startPeriod=#{q.start}" if q.start + url += "&endPeriod=#{q.end}" if q.end + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url + +createV2AvailUrl = (q, s) -> + validateDataForV2 q, s + url = createEntryPoint s + url += 'availableconstraint' + fc = parseFlow q.flow + url += "/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" + url += if q.key == "all" then "*/" else "#{q.key}/" + url += if q.component == "all" then "*" else "#{q.component}" + url += "?mode=#{q.mode}&references=#{q.references}" + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url + +createShortAvailabilityQuery = (q, s, api_number) -> + if api_number < ApiNumber.v2_0_0 + createShortV1AvailUrl q, s + else + createShortV2AvailUrl q, s + +createAvailabilityQuery = (q, s, api_number) -> + if api_number < ApiNumber.v2_0_0 + createV1AvailUrl q, s + else + createV2AvailUrl q, s + +handler = class Handler + + handle: (q, s, skip) -> + api_number = ApiNumber[getKeyFromVersion(s.api)] + if api_number < ApiNumber.v1_3_0 + throw Error "Availability query not supported in #{s.api}" + else if skip + createShortAvailabilityQuery(q, s, api_number) + else + createAvailabilityQuery(q, s, api_number) + +exports.AvailabilityQueryHandler = handler diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee new file mode 100644 index 0000000..3319f35 --- /dev/null +++ b/src/utils/url-generator-common.coffee @@ -0,0 +1,28 @@ +createEntryPoint = (s) -> + throw ReferenceError "#{s.url} is not a valid service" unless s.url + url = s.url + url = s.url + '/' unless s.url.endsWith('/') + url + +parseFlow = (f) -> + parts = f.split "," + if parts.length == 1 + ["*", f, "*"] + else if parts.length == 2 + parts.concat ["*"] + else + parts + +validateDataForV2 = (q, s) -> + if q.provider isnt "all" + throw Error "provider not allowed in #{s.api}" + if q.start + throw Error "start not allowed in #{s.api}" + if q.end + throw Error "end not allowed in #{s.api}" + if q.key.indexOf("\+") > -1 + throw Error "+ not allowed in key in #{s.api}" + +exports.createEntryPoint = createEntryPoint +exports.parseFlow = parseFlow +exports.validateDataForV2 = validateDataForV2 diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 6d4ff7a..0ab02a0 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -7,6 +7,7 @@ {MetadataReferencesExcluded} = require '../metadata/metadata-references' {MetadataReferencesSpecial} = require '../metadata/metadata-references' {VersionNumber} = require '../utils/sdmx-patterns' +{AvailabilityQueryHandler} = require '../utils/url-generator-availability' itemAllowed = (resource, api) -> api isnt ApiVersion.v1_0_0 and @@ -212,90 +213,6 @@ createShortMetadataQuery = (q, s) -> ) u -createV1AvailUrl = (q, s) -> - url = createEntryPoint s - url += 'availableconstraint' - url += "/#{q.flow}/#{q.key}/#{q.provider}/#{q.component}" - url += "?mode=#{q.mode}&references=#{q.references}" - url += "&startPeriod=#{q.start}" if q.start - url += "&endPeriod=#{q.end}" if q.end - url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter - url - -createV2AvailUrl = (q, s) -> - validateDataForV2 q, s - url = createEntryPoint s - url += 'availableconstraint' - fc = parseFlow q.flow - url += "/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" - url += if q.key == "all" then "*/" else "#{q.key}/" - url += if q.component == "all" then "*" else "#{q.component}" - url += "?mode=#{q.mode}&references=#{q.references}" - url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter - url - -createAvailabilityQuery = (q, s) -> - if s.api in preSdmx3 - createV1AvailUrl q, s - else - createV2AvailUrl q, s - -handleAvailabilityPathParams = (q) -> - path = [] - path.push q.component unless q.component is 'all' - path.push q.provider if q.provider isnt 'all' or path.length - path.push q.key if q.key isnt 'all' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -handleAvailabilityQueryParams = (q) -> - p = [] - p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter - p.push "endPeriod=#{q.end}" if q.end - p.push "startPeriod=#{q.start}" if q.start - p.push "mode=#{q.mode}" unless q.mode is 'exact' - p.push "references=#{q.references}" unless q.references is 'none' - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -handleAvailabilityV2PathParams = (q) -> - path = [] - path.push q.component unless q.component is 'all' - k = if q.key is 'all' then '*' else q.key - path.push k if k isnt '*' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -handleAvailabilityV2QueryParams = (q) -> - p = [] - p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter - p.push "mode=#{q.mode}" unless q.mode is 'exact' - p.push "references=#{q.references}" unless q.references is 'none' - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -createShortV1AvailUrl = (q, s) -> - u = createEntryPoint s - u += "availableconstraint/#{q.flow}" - u += handleAvailabilityPathParams(q) - u += handleAvailabilityQueryParams(q) - u - -createShortV2AvailUrl = (q, s) -> - validateDataForV2 q, s - u = createEntryPoint s - u += "availableconstraint/dataflow/" - fc = parseFlow q.flow - u += "#{fc[0]}/#{fc[1]}" - pp = handleAvailabilityV2PathParams(q) - if fc[2] isnt "*" or pp isnt "" - u += "/#{fc[2]}" - u += pp - u += handleAvailabilityV2QueryParams(q) - u - -createShortAvailabilityQuery = (q, s) -> - if s.api in preSdmx3 - createShortV1AvailUrl q, s - else - createShortV2AvailUrl q, s - createSchemaQuery = (q, s) -> u = createEntryPoint s v = if s.api is ApiVersion.v2_0_0 and q.version is "latest" then "~"\ @@ -417,14 +334,6 @@ checkMultipleVersions = (q, s) -> else checkVersionWithAll q, s, v -handleAvailabilityQuery = (qry, srv, skip) -> - if srv.api in excluded - throw Error "Availability query not supported in #{srv.api}" - else if skip - createShortAvailabilityQuery(qry, srv) - else - createAvailabilityQuery(qry, srv) - handleDataQuery = (qry, srv, skip) -> checkMultipleItems(qry.provider, srv, 'providers') if skip @@ -456,7 +365,7 @@ generator = class Generator getUrl: (@query, service, skipDefaults) -> @service = service ? ApiVersion.LATEST if @query?.mode? - handleAvailabilityQuery(@query, @service, skipDefaults) + new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) else if @query?.flow? handleDataQuery(@query, @service, skipDefaults) else if @query?.resource? From d41ac37176bce98a68849099bbbb034400e17127 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 12:09:01 +0200 Subject: [PATCH 04/47] Move generator for schema queries --- src/utils/url-generator-common.coffee | 10 ++++++ src/utils/url-generator-schema.coffee | 49 +++++++++++++++++++++++++++ src/utils/url-generator.coffee | 48 ++------------------------ 3 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 src/utils/url-generator-schema.coffee diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee index 3319f35..fc74e37 100644 --- a/src/utils/url-generator-common.coffee +++ b/src/utils/url-generator-common.coffee @@ -1,3 +1,6 @@ +{ApiVersion} = require '../utils/api-version' +{VersionNumber} = require '../utils/sdmx-patterns' + createEntryPoint = (s) -> throw ReferenceError "#{s.url} is not a valid service" unless s.url url = s.url @@ -23,6 +26,13 @@ validateDataForV2 = (q, s) -> if q.key.indexOf("\+") > -1 throw Error "+ not allowed in key in #{s.api}" +checkVersion = (q, s) -> + v = q.version + if s.api isnt ApiVersion.v2_0_0 + throw Error "Semantic versioning not allowed in #{s.api}" \ + unless v is 'latest' or v.match VersionNumber + exports.createEntryPoint = createEntryPoint exports.parseFlow = parseFlow exports.validateDataForV2 = validateDataForV2 +exports.checkVersion = checkVersion \ No newline at end of file diff --git a/src/utils/url-generator-schema.coffee b/src/utils/url-generator-schema.coffee new file mode 100644 index 0000000..0fd9a4d --- /dev/null +++ b/src/utils/url-generator-schema.coffee @@ -0,0 +1,49 @@ +{ApiResources, ApiVersion} = require '../utils/api-version' +{createEntryPoint, checkVersion} = require '../utils/url-generator-common' + +createSchemaQuery = (q, s) -> + u = createEntryPoint s + v = if s.api is ApiVersion.v2_0_0 and q.version is "latest" then "~"\ + else q.version + u += "schema/#{q.context}/#{q.agency}/#{q.id}/#{v}" + if s.api is ApiVersion.v2_0_0 + u += "?dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + else + u += "?explicitMeasure=#{q.explicit}" + u += "&dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + u + +handleSchemaQueryParams = (q) -> + p = [] + p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + p.push "explicitMeasure=#{q.explicit}" if q.explicit + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +createShortSchemaQuery = (q, s) -> + u = createEntryPoint s + u += "schema/#{q.context}/#{q.agency}/#{q.id}" + u += "/#{q.version}" unless q.version is 'latest' or q.version is '~' + u += handleSchemaQueryParams(q) + u + +checkContext = (q, s) -> + api = s.api.replace /\./g, '_' + throw Error "#{q.context} not allowed in #{s.api}" \ + unless q.context in ApiResources[api] + +checkExplicit = (q, s) -> + if q.explicit and s and s.api and s.api is ApiVersion.v2_0_0 + throw Error "explicit parameter not allowed in #{s.api}" + +handler = class Handler + + handle: (q, s, skip) -> + checkContext(q, s) + checkExplicit(q, s) + checkVersion(q, s) + if skip + createShortSchemaQuery(q, s) + else + createSchemaQuery(q, s) + +exports.SchemaQueryHandler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 0ab02a0..ec4db23 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -8,6 +8,7 @@ {MetadataReferencesSpecial} = require '../metadata/metadata-references' {VersionNumber} = require '../utils/sdmx-patterns' {AvailabilityQueryHandler} = require '../utils/url-generator-availability' +{SchemaQueryHandler} = require '../utils/url-generator-schema' itemAllowed = (resource, api) -> api isnt ApiVersion.v1_0_0 and @@ -213,31 +214,6 @@ createShortMetadataQuery = (q, s) -> ) u -createSchemaQuery = (q, s) -> - u = createEntryPoint s - v = if s.api is ApiVersion.v2_0_0 and q.version is "latest" then "~"\ - else q.version - u += "schema/#{q.context}/#{q.agency}/#{q.id}/#{v}" - if s.api is ApiVersion.v2_0_0 - u += "?dimensionAtObservation=#{q.obsDimension}" if q.obsDimension - else - u += "?explicitMeasure=#{q.explicit}" - u += "&dimensionAtObservation=#{q.obsDimension}" if q.obsDimension - u - -handleSchemaQueryParams = (q) -> - p = [] - p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension - p.push "explicitMeasure=#{q.explicit}" if q.explicit - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -createShortSchemaQuery = (q, s) -> - u = createEntryPoint s - u += "schema/#{q.context}/#{q.agency}/#{q.id}" - u += "/#{q.version}" unless q.version is 'latest' or q.version is '~' - u += handleSchemaQueryParams(q) - u - excluded = [ ApiVersion.v1_0_0 ApiVersion.v1_0_1 @@ -301,17 +277,6 @@ checkReferences = (q, s) -> if (s.api in preSdmx3 and q.references is 'ancestors') throw Error "ancestors not allowed as reference in #{s.api}" - -checkContext = (q, s) -> - if s and s.api - api = s.api.replace /\./g, '_' - throw Error "#{q.context} not allowed in #{s.api}" \ - unless q.context in ApiResources[api] - -checkExplicit = (q, s) -> - if q.explicit and s and s.api and s.api is ApiVersion.v2_0_0 - throw Error "explicit parameter not allowed in #{s.api}" - checkVersion = (q, s) -> v = q.version if s and s.api and s.api isnt ApiVersion.v2_0_0 @@ -351,15 +316,6 @@ handleMetadataQuery = (qry, srv, skip) -> else createMetadataQuery(qry, srv) -handleSchemaQuery = (qry, srv, skip) -> - checkContext(qry, srv) - checkExplicit(qry, srv) - checkVersion(qry, srv) - if skip - createShortSchemaQuery(qry, srv) - else - createSchemaQuery(qry, srv) - generator = class Generator getUrl: (@query, service, skipDefaults) -> @@ -371,7 +327,7 @@ generator = class Generator else if @query?.resource? handleMetadataQuery(@query, @service, skipDefaults) else if @query?.context? - handleSchemaQuery(@query, @service, skipDefaults) + new SchemaQueryHandler().handle(@query, @service, skipDefaults) else throw TypeError "#{@query} is not a valid SDMX data, metadata or \ availability query" From 5cc2736d6f93060e255f18a72a708a5c9e604976 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 12:55:22 +0200 Subject: [PATCH 05/47] Move generator for data queries --- src/utils/url-generator-data.coffee | 131 ++++++++++++++++++++++++++ src/utils/url-generator.coffee | 141 +--------------------------- 2 files changed, 133 insertions(+), 139 deletions(-) create mode 100644 src/utils/url-generator-data.coffee diff --git a/src/utils/url-generator-data.coffee b/src/utils/url-generator-data.coffee new file mode 100644 index 0000000..ecf5458 --- /dev/null +++ b/src/utils/url-generator-data.coffee @@ -0,0 +1,131 @@ +{ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' +{createEntryPoint, validateDataForV2, parseFlow} = require '../utils/url-generator-common' +{DataDetail} = require '../data/data-detail' + +translateDetail = (detail) -> + if detail == DataDetail.NO_DATA + "attributes=dataset,series&measures=none" + else if detail == DataDetail.DATA_ONLY + "attributes=none&measures=all" + else if detail == DataDetail.SERIES_KEYS_ONLY + "attributes=none&measures=none" + else + "attributes=dsd&measures=all" + +createV1DataUrl = (query, service) -> + url = createEntryPoint service + url += "data/#{query.flow}/#{query.key}/#{query.provider}?" + if query.obsDimension + url += "dimensionAtObservation=#{query.obsDimension}&" + url += "detail=#{query.detail}" + if (service.api isnt ApiVersion.v1_0_0 and + service.api isnt ApiVersion.v1_0_1 and + service.api isnt ApiVersion.v1_0_2) + url += "&includeHistory=#{query.history}" + url += "&startPeriod=#{query.start}" if query.start + url += "&endPeriod=#{query.end}" if query.end + url += "&updatedAfter=#{query.updatedAfter}" if query.updatedAfter + url += "&firstNObservations=#{query.firstNObs}" if query.firstNObs + url += "&lastNObservations=#{query.lastNObs}" if query.lastNObs + url + +createV2DataUrl = (q, s) -> + validateDataForV2 q, s + url = createEntryPoint s + fc = parseFlow q.flow + url += "data/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" + url += if q.key == "all" then "*?" else "#{q.key}?" + if q.obsDimension + url += "dimensionAtObservation=#{q.obsDimension}&" + url += translateDetail q.detail + url += "&includeHistory=#{q.history}" + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url += "&firstNObservations=#{q.firstNObs}" if q.firstNObs + url += "&lastNObservations=#{q.lastNObs}" if q.lastNObs + url + +createDataQuery = (q, s, a) -> + if a < ApiNumber.v2_0_0 + createV1DataUrl q, s + else + createV2DataUrl q, s + +handleDataPathParams = (q) -> + path = [] + path.push q.provider unless q.provider is 'all' + path.push q.key if q.key isnt 'all' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +handleData2PathParams = (q) -> + path = [] + path.push q.key if q.key isnt 'all' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +hasHistory = (q, s) -> + if (s.api isnt ApiVersion.v1_0_0 and + s.api isnt ApiVersion.v1_0_1 and + s.api isnt ApiVersion.v1_0_2 and + q.history) then true else false + +handleDataQueryParams = (q, s) -> + p = [] + p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + p.push "detail=#{q.detail}" unless q.detail is 'full' + p.push "includeHistory=#{q.history}" if hasHistory(q, s) + p.push "startPeriod=#{q.start}" if q.start + p.push "endPeriod=#{q.end}" if q.end + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs + p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +handleData2QueryParams = (q, s) -> + p = [] + p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + p.push "#{translateDetail q.detail}" unless q.detail is 'full' + p.push "includeHistory=#{q.history}" if hasHistory(q, s) + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs + p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +createShortV1Url = (q, s) -> + u = createEntryPoint s + u += "data/#{q.flow}" + u += handleDataPathParams(q) + u += handleDataQueryParams(q, s) + u + +createShortV2Url = (q, s) -> + validateDataForV2 q, s + u = createEntryPoint s + fc = parseFlow q.flow + u += "data/dataflow/#{fc[0]}/#{fc[1]}" + pp = handleData2PathParams(q) + if fc[2] isnt "*" or pp isnt '' + u += "/#{fc[2]}" + u += pp + u += handleData2QueryParams(q, s) + u + +createShortDataQuery = (q, s, a) -> + if a < ApiNumber.v2_0_0 + createShortV1Url q, s + else + createShortV2Url q, s + +checkMultipleItems = (i, s, r, a) -> + if a < ApiNumber.v1_3_0 and /\+/.test i + throw Error "Multiple #{r} not allowed in #{s.api}" + +handler = class Handler + + handle: (q, s, skip) -> + api = ApiNumber[getKeyFromVersion(s.api)] + checkMultipleItems(q.provider, s, 'providers', api) + if skip + createShortDataQuery(q, s, api) + else + createDataQuery(q, s, api) + +exports.DataQueryHandler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index ec4db23..3627150 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -9,6 +9,7 @@ {VersionNumber} = require '../utils/sdmx-patterns' {AvailabilityQueryHandler} = require '../utils/url-generator-availability' {SchemaQueryHandler} = require '../utils/url-generator-schema' +{DataQueryHandler} = require '../utils/url-generator-data' itemAllowed = (resource, api) -> api isnt ApiVersion.v1_0_0 and @@ -23,137 +24,6 @@ createEntryPoint = (s) -> url = s.url + '/' unless s.url.endsWith('/') url -createV1DataUrl = (query, service) -> - url = createEntryPoint service - url += "data/#{query.flow}/#{query.key}/#{query.provider}?" - if query.obsDimension - url += "dimensionAtObservation=#{query.obsDimension}&" - url += "detail=#{query.detail}" - if (service.api isnt ApiVersion.v1_0_0 and - service.api isnt ApiVersion.v1_0_1 and - service.api isnt ApiVersion.v1_0_2) - url += "&includeHistory=#{query.history}" - url += "&startPeriod=#{query.start}" if query.start - url += "&endPeriod=#{query.end}" if query.end - url += "&updatedAfter=#{query.updatedAfter}" if query.updatedAfter - url += "&firstNObservations=#{query.firstNObs}" if query.firstNObs - url += "&lastNObservations=#{query.lastNObs}" if query.lastNObs - url - -parseFlow = (flow) -> - parts = flow.split "," - if parts.length == 1 - ["*", flow, "*"] - else if parts.length == 2 - parts.concat ["*"] - else - parts - -translateDetail = (detail) -> - if detail == DataDetail.NO_DATA - "attributes=dataset,series&measures=none" - else if detail == DataDetail.DATA_ONLY - "attributes=none&measures=all" - else if detail == DataDetail.SERIES_KEYS_ONLY - "attributes=none&measures=none" - else - "attributes=dsd&measures=all" - -validateDataForV2 = (q, s) -> - if q.provider isnt "all" - throw Error "provider not allowed in #{s.api}" - if q.start - throw Error "start not allowed in #{s.api}" - if q.end - throw Error "end not allowed in #{s.api}" - if q.key.indexOf("\+") > -1 - throw Error "+ not allowed in key in #{s.api}" - -createV2DataUrl = (q, s) -> - validateDataForV2 q, s - url = createEntryPoint s - fc = parseFlow q.flow - url += "data/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" - url += if q.key == "all" then "*?" else "#{q.key}?" - if q.obsDimension - url += "dimensionAtObservation=#{q.obsDimension}&" - url += translateDetail q.detail - url += "&includeHistory=#{q.history}" - url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter - url += "&firstNObservations=#{q.firstNObs}" if q.firstNObs - url += "&lastNObservations=#{q.lastNObs}" if q.lastNObs - url - -createDataQuery = (query, service) -> - if service.api in preSdmx3 - createV1DataUrl query, service - else - createV2DataUrl query, service - -handleDataPathParams = (q) -> - path = [] - path.push q.provider unless q.provider is 'all' - path.push q.key if q.key isnt 'all' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -handleData2PathParams = (q) -> - path = [] - path.push q.key if q.key isnt 'all' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -hasHistory = (q, s) -> - if (s.api isnt ApiVersion.v1_0_0 and - s.api isnt ApiVersion.v1_0_1 and - s.api isnt ApiVersion.v1_0_2 and - q.history) then true else false - -handleDataQueryParams = (q, s) -> - p = [] - p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension - p.push "detail=#{q.detail}" unless q.detail is 'full' - p.push "includeHistory=#{q.history}" if hasHistory(q, s) - p.push "startPeriod=#{q.start}" if q.start - p.push "endPeriod=#{q.end}" if q.end - p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter - p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs - p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -handleData2QueryParams = (q, s) -> - p = [] - p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension - p.push "#{translateDetail q.detail}" unless q.detail is 'full' - p.push "includeHistory=#{q.history}" if hasHistory(q, s) - p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter - p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs - p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -createShortV1Url = (q, s) -> - u = createEntryPoint s - u += "data/#{q.flow}" - u += handleDataPathParams(q) - u += handleDataQueryParams(q, s) - u - -createShortV2Url = (q, s) -> - validateDataForV2 q, s - u = createEntryPoint s - fc = parseFlow q.flow - u += "data/dataflow/#{fc[0]}/#{fc[1]}" - pp = handleData2PathParams(q) - if fc[2] isnt "*" or pp isnt '' - u += "/#{fc[2]}" - u += pp - u += handleData2QueryParams(q, s) - u - -createShortDataQuery = (q, s) -> - if s.api in preSdmx3 - createShortV1Url q, s - else - createShortV2Url q, s - toApiKeywords = (q, s, value, isVersion = false) -> v = value if s.api is ApiVersion.v2_0_0 and v is "all" @@ -299,13 +169,6 @@ checkMultipleVersions = (q, s) -> else checkVersionWithAll q, s, v -handleDataQuery = (qry, srv, skip) -> - checkMultipleItems(qry.provider, srv, 'providers') - if skip - createShortDataQuery(qry, srv) - else - createDataQuery(qry, srv) - handleMetadataQuery = (qry, srv, skip) -> checkApiVersion(qry, srv) checkDetail(qry, srv) @@ -323,7 +186,7 @@ generator = class Generator if @query?.mode? new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) else if @query?.flow? - handleDataQuery(@query, @service, skipDefaults) + new DataQueryHandler().handle(@query, @service, skipDefaults) else if @query?.resource? handleMetadataQuery(@query, @service, skipDefaults) else if @query?.context? From b8ed28f06880cb50d1312e4525a00fa1a85ca6cb Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 13:42:11 +0200 Subject: [PATCH 06/47] Move checkMultipleItems to common utils --- src/utils/url-generator-common.coffee | 9 +++++++-- src/utils/url-generator-data.coffee | 7 ++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee index fc74e37..6b407e5 100644 --- a/src/utils/url-generator-common.coffee +++ b/src/utils/url-generator-common.coffee @@ -1,4 +1,4 @@ -{ApiVersion} = require '../utils/api-version' +{ApiVersion, ApiNumber} = require '../utils/api-version' {VersionNumber} = require '../utils/sdmx-patterns' createEntryPoint = (s) -> @@ -32,7 +32,12 @@ checkVersion = (q, s) -> throw Error "Semantic versioning not allowed in #{s.api}" \ unless v is 'latest' or v.match VersionNumber +checkMultipleItems = (i, s, r, a) -> + if a < ApiNumber.v1_3_0 and /\+/.test i + throw Error "Multiple #{r} not allowed in #{s.api}" + exports.createEntryPoint = createEntryPoint exports.parseFlow = parseFlow exports.validateDataForV2 = validateDataForV2 -exports.checkVersion = checkVersion \ No newline at end of file +exports.checkVersion = checkVersion +exports.checkMultipleItems = checkMultipleItems \ No newline at end of file diff --git a/src/utils/url-generator-data.coffee b/src/utils/url-generator-data.coffee index ecf5458..cc75e5a 100644 --- a/src/utils/url-generator-data.coffee +++ b/src/utils/url-generator-data.coffee @@ -1,5 +1,6 @@ {ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' -{createEntryPoint, validateDataForV2, parseFlow} = require '../utils/url-generator-common' +{createEntryPoint, validateDataForV2, parseFlow, checkMultipleItems} = + require '../utils/url-generator-common' {DataDetail} = require '../data/data-detail' translateDetail = (detail) -> @@ -114,10 +115,6 @@ createShortDataQuery = (q, s, a) -> else createShortV2Url q, s -checkMultipleItems = (i, s, r, a) -> - if a < ApiNumber.v1_3_0 and /\+/.test i - throw Error "Multiple #{r} not allowed in #{s.api}" - handler = class Handler handle: (q, s, skip) -> From 6a414d5be1df310a6cafa8b7452326f6fe5d92b3 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 13:42:51 +0200 Subject: [PATCH 07/47] Move generator for structure queries --- src/utils/url-generator-metadata.coffee | 148 ++++++++++++++++++++ src/utils/url-generator.coffee | 179 +----------------------- 2 files changed, 150 insertions(+), 177 deletions(-) create mode 100644 src/utils/url-generator-metadata.coffee diff --git a/src/utils/url-generator-metadata.coffee b/src/utils/url-generator-metadata.coffee new file mode 100644 index 0000000..e8056ea --- /dev/null +++ b/src/utils/url-generator-metadata.coffee @@ -0,0 +1,148 @@ +{ApiResources, ApiVersion, ApiNumber, getKeyFromVersion} = + require '../utils/api-version' +{createEntryPoint, checkMultipleItems} = + require '../utils/url-generator-common' +{MetadataReferences, MetadataReferencesExcluded, MetadataReferencesSpecial} = + require '../metadata/metadata-references' +{MetadataDetail} = require '../metadata/metadata-detail' +{VersionNumber} = require '../utils/sdmx-patterns' +{isItemScheme} = require '../metadata/metadata-type' + +itemAllowed = (r, a) -> + a > ApiNumber.v1_0_2 and + ((r isnt 'hierarchicalcodelist' and isItemScheme(r)) or + (a > ApiNumber.v1_1_0 and r is 'hierarchicalcodelist')) + +toApiKeywords = (q, s, value, isVersion = false) -> + v = value + if s.api is ApiVersion.v2_0_0 and v is "all" + v = "*" + else if s.api isnt ApiVersion.v2_0_0 and v is "*" + v = "all" + else if s.api is ApiVersion.v2_0_0 and v is "latest" + v = "~" + else if s.api is ApiVersion.v2_0_0 and not isVersion and v.indexOf("\+") > -1 + v = v.replace /\+/, "," + else if s.api isnt ApiVersion.v2_0_0 and v.indexOf(",") > -1 + v = v.replace /,/, "+" + v + +createMetadataQuery = (q, s, a) -> + url = createEntryPoint s + url += "structure/" unless a < ApiNumber.v2_0_0 + res = toApiKeywords q, s, q.resource + agency = toApiKeywords q, s, q.agency + id = toApiKeywords q, s, q.id + item = toApiKeywords q, s, q.item + v = if s.api is ApiVersion.v2_0_0 and q.version is "latest"\ + then "~" else q.version + url += "#{res}/#{agency}/#{id}/#{v}" + url += "/#{item}" if itemAllowed(q.resource, a) + url += "?detail=#{q.detail}&references=#{q.references}" + url + +handleMetaPathParams = (q, s, u, a) -> + path = [] + if q.item isnt 'all' and q.item isnt '*' and itemAllowed(q.resource, a)\ + then path.push toApiKeywords q, s, q.item + if (q.version isnt 'latest' and q.version isnt '~') or path.length\ + then path.push toApiKeywords q, s, q.version, true + if (q.id isnt 'all' and q.id isnt '*') or path.length + then path.push toApiKeywords q, s, q.id + if (q.agency isnt 'all' and q.agency isnt '*') or path.length + then path.push toApiKeywords q, s, q.agency + if path.length then u = u + '/' + path.reverse().join('/') + u + +handleMetaQueryParams = (q, u, hd, hr) -> + if hd or hr then u += '?' + if hd then u += "detail=#{q.detail}" + if hd and hr then u += '&' + if hr then u += "references=#{q.references}" + u + +createShortMetadataQuery = (q, s, a) -> + u = createEntryPoint s + u += "structure/" unless a < ApiNumber.v2_0_0 + r = toApiKeywords q, s, q.resource + u += "#{r}" + u = handleMetaPathParams(q, s, u, a) + u = handleMetaQueryParams( + q, u, q.detail isnt MetadataDetail.FULL, + q.references isnt MetadataReferences.NONE + ) + u + +checkVersionWithAll = (q, s, v, a) -> + if a < ApiNumber.v2_0_0 + throw Error "Semantic versioning not allowed in #{s.api}" \ + unless v is 'latest' or v is 'all' or v.match VersionNumber + +checkMultipleVersions = (q, s, a) -> + v = q.version + if v.indexOf("\+") > -1 + for i in v.split "+" + checkVersionWithAll q, s, i, a + else if v.indexOf(",") > -1 + for i in v.split "," + checkVersionWithAll q, s, i, a + else + checkVersionWithAll q, s, v, a + +checkApiVersion = (q, s, a) -> + checkMultipleItems q.agency, s, 'agencies', a + checkMultipleItems q.id, s, 'IDs', a + checkMultipleItems q.version, s, 'versions', a + checkMultipleItems q.item, s, 'items', a + checkMultipleVersions q, s, a + +checkDetail = (q, s, a) -> + if (a < ApiNumber.v1_3_0 and (q.detail is 'referencepartial' or + q.detail is 'allcompletestubs' or q.detail is 'referencecompletestubs')) + throw Error "#{q.detail} not allowed in #{s.api}" + + if (a < ApiNumber.v2_0_0 and q.detail is 'raw') + throw Error "raw not allowed in #{s.api}" + +checkResource = (q, s, r) -> + api = s.api.replace /\./g, '_' + throw Error "#{r} not allowed in #{s.api}" unless r in ApiResources[api] \ + or (s.api is ApiVersion.v2_0_0 and r is "*") + +checkResources = (q, s, a) -> + r = q.resource + if a < ApiNumber.v2_0_0 + checkResource q, s, r + else if r.indexOf("\+") > -1 + for i in r.split "+" + checkResource q, s, i + else if r.indexOf(",") > -1 + for i in r.split "," + checkResource q, s, i + else + checkResource q, s, r + +checkReferences = (q, s, a) -> + api = s.api.replace /\./g, '_' + throw Error "#{q.references} not allowed as reference in #{s.api}" \ + unless (q.references in ApiResources[api] or \ + q.references in Object.values MetadataReferencesSpecial) and \ + q.references not in MetadataReferencesExcluded + + if (a < ApiNumber.v2_0_0 and q.references is 'ancestors') + throw Error "ancestors not allowed as reference in #{s.api}" + +handler = class Handler + + handle: (q, s, skip) -> + a = ApiNumber[getKeyFromVersion(s.api)] + checkApiVersion(q, s, a) + checkDetail(q, s, a) + checkResources(q, s, a) + checkReferences(q, s, a) if q.references + if skip + createShortMetadataQuery(q, s, a) + else + createMetadataQuery(q, s, a) + +exports.MetadataQueryHandler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 3627150..d534bbf 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -1,183 +1,8 @@ {ApiVersion} = require '../utils/api-version' -{ApiResources} = require '../utils/api-version' -{isItemScheme} = require '../metadata/metadata-type' -{DataDetail} = require '../data/data-detail' -{MetadataDetail} = require '../metadata/metadata-detail' -{MetadataReferences} = require '../metadata/metadata-references' -{MetadataReferencesExcluded} = require '../metadata/metadata-references' -{MetadataReferencesSpecial} = require '../metadata/metadata-references' -{VersionNumber} = require '../utils/sdmx-patterns' {AvailabilityQueryHandler} = require '../utils/url-generator-availability' {SchemaQueryHandler} = require '../utils/url-generator-schema' {DataQueryHandler} = require '../utils/url-generator-data' - -itemAllowed = (resource, api) -> - api isnt ApiVersion.v1_0_0 and - api isnt ApiVersion.v1_0_1 and - api isnt ApiVersion.v1_0_2 and - ((resource isnt 'hierarchicalcodelist' and isItemScheme(resource)) or - (api isnt ApiVersion.v1_1_0 and resource is 'hierarchicalcodelist')) - -createEntryPoint = (s) -> - throw ReferenceError "#{s.url} is not a valid service" unless s.url - url = s.url - url = s.url + '/' unless s.url.endsWith('/') - url - -toApiKeywords = (q, s, value, isVersion = false) -> - v = value - if s.api is ApiVersion.v2_0_0 and v is "all" - v = "*" - else if s.api isnt ApiVersion.v2_0_0 and v is "*" - v = "all" - else if s.api is ApiVersion.v2_0_0 and v is "latest" - v = "~" - else if s.api is ApiVersion.v2_0_0 and not isVersion and v.indexOf("\+") > -1 - v = v.replace /\+/, "," - else if s.api isnt ApiVersion.v2_0_0 and v.indexOf(",") > -1 - v = v.replace /,/, "+" - v - -createMetadataQuery = (q, s) -> - url = createEntryPoint s - url += "structure/" unless s.api in preSdmx3 - res = toApiKeywords q, s, q.resource - agency = toApiKeywords q, s, q.agency - id = toApiKeywords q, s, q.id - item = toApiKeywords q, s, q.item - v = if s.api is ApiVersion.v2_0_0 and q.version is "latest"\ - then "~" else q.version - url += "#{res}/#{agency}/#{id}/#{v}" - url += "/#{item}" if itemAllowed(q.resource, s.api) - url += "?detail=#{q.detail}&references=#{q.references}" - url - -handleMetaPathParams = (q, s, u) -> - path = [] - if q.item isnt 'all' and q.item isnt '*' and itemAllowed(q.resource, s.api)\ - then path.push toApiKeywords q, s, q.item - if (q.version isnt 'latest' and q.version isnt '~') or path.length\ - then path.push toApiKeywords q, s, q.version, true - if (q.id isnt 'all' and q.id isnt '*') or path.length - then path.push toApiKeywords q, s, q.id - if (q.agency isnt 'all' and q.agency isnt '*') or path.length - then path.push toApiKeywords q, s, q.agency - if path.length then u = u + '/' + path.reverse().join('/') - u - -handleMetaQueryParams = (q, u, hd, hr) -> - if hd or hr then u += '?' - if hd then u += "detail=#{q.detail}" - if hd and hr then u += '&' - if hr then u += "references=#{q.references}" - u - -createShortMetadataQuery = (q, s) -> - u = createEntryPoint s - u += "structure/" unless s.api in preSdmx3 - r = toApiKeywords q, s, q.resource - u += "#{r}" - u = handleMetaPathParams(q, s, u) - u = handleMetaQueryParams( - q, u, q.detail isnt MetadataDetail.FULL, - q.references isnt MetadataReferences.NONE - ) - u - -excluded = [ - ApiVersion.v1_0_0 - ApiVersion.v1_0_1 - ApiVersion.v1_0_2 - ApiVersion.v1_1_0 - ApiVersion.v1_2_0 -] - -preSdmx3 = ([ - ApiVersion.v1_3_0 - ApiVersion.v1_4_0 - ApiVersion.v1_5_0 -].concat excluded) - -checkMultipleItems = (i, s, r) -> - if s.api in excluded and /\+/.test i - throw Error "Multiple #{r} not allowed in #{s.api}" - -checkApiVersion = (q, s) -> - checkMultipleItems q.agency, s, 'agencies' - checkMultipleItems q.id, s, 'IDs' - checkMultipleItems q.version, s, 'versions' - checkMultipleItems q.item, s, 'items' - checkMultipleVersions q, s - -checkDetail = (q, s) -> - if (s.api in excluded and (q.detail is 'referencepartial' or - q.detail is 'allcompletestubs' or q.detail is 'referencecompletestubs')) - throw Error "#{q.detail} not allowed in #{s.api}" - - if (s.api in preSdmx3 and q.detail is 'raw') - throw Error "raw not allowed in #{s.api}" - -checkResource = (q, s, r) -> - if s and s.api - api = s.api.replace /\./g, '_' - throw Error "#{r} not allowed in #{s.api}" unless r in ApiResources[api] \ - or (s.api is ApiVersion.v2_0_0 and r is "*") - -checkResources = (q, s) -> - r = q.resource - if s.api in preSdmx3 - checkResource q, s, r - else if r.indexOf("\+") > -1 - for i in r.split "+" - checkResource q, s, i - else if r.indexOf(",") > -1 - for i in r.split "," - checkResource q, s, i - else - checkResource q, s, r - -checkReferences = (q, s) -> - if s and s.api - api = s.api.replace /\./g, '_' - throw Error "#{q.references} not allowed as reference in #{s.api}" \ - unless (q.references in ApiResources[api] or \ - q.references in Object.values MetadataReferencesSpecial) and \ - q.references not in MetadataReferencesExcluded - - if (s.api in preSdmx3 and q.references is 'ancestors') - throw Error "ancestors not allowed as reference in #{s.api}" - -checkVersion = (q, s) -> - v = q.version - if s and s.api and s.api isnt ApiVersion.v2_0_0 - throw Error "Semantic versioning not allowed in #{s.api}" \ - unless v is 'latest' or v.match VersionNumber - -checkVersionWithAll = (q, s, v) -> - if s and s.api and s.api isnt ApiVersion.v2_0_0 - throw Error "Semantic versioning not allowed in #{s.api}" \ - unless v is 'latest' or v is 'all' or v.match VersionNumber - -checkMultipleVersions = (q, s) -> - v = q.version - if v.indexOf("\+") > -1 - for i in v.split "+" - checkVersionWithAll q, s, i - else if v.indexOf(",") > -1 - for i in v.split "," - checkVersionWithAll q, s, i - else - checkVersionWithAll q, s, v - -handleMetadataQuery = (qry, srv, skip) -> - checkApiVersion(qry, srv) - checkDetail(qry, srv) - checkResources(qry, srv) - checkReferences(qry, srv) if qry.references - if skip - createShortMetadataQuery(qry, srv) - else - createMetadataQuery(qry, srv) +{MetadataQueryHandler} = require '../utils/url-generator-metadata' generator = class Generator @@ -188,7 +13,7 @@ generator = class Generator else if @query?.flow? new DataQueryHandler().handle(@query, @service, skipDefaults) else if @query?.resource? - handleMetadataQuery(@query, @service, skipDefaults) + new MetadataQueryHandler().handle(@query, @service, skipDefaults) else if @query?.context? new SchemaQueryHandler().handle(@query, @service, skipDefaults) else From 6d1caa68e065675882ee708c315b859c1c13d6a2 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 15:19:43 +0200 Subject: [PATCH 08/47] Improve error handling when service is missing --- src/utils/url-generator.coffee | 5 +++-- test/utils/url-generator.test.coffee | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index d534bbf..eb01a11 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -6,8 +6,9 @@ generator = class Generator - getUrl: (@query, service, skipDefaults) -> - @service = service ? ApiVersion.LATEST + getUrl: (@query, @service, skipDefaults) -> + throw ReferenceError "#{@service} is not a valid service"\ + unless @service and @service.url if @query?.mode? new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) else if @query?.flow? diff --git a/test/utils/url-generator.test.coffee b/test/utils/url-generator.test.coffee index b04c294..57416cb 100644 --- a/test/utils/url-generator.test.coffee +++ b/test/utils/url-generator.test.coffee @@ -11,12 +11,20 @@ should = require('chai').should() describe 'URL Generator (generic)', -> it 'throws an exception if no query is supplied', -> - test = -> new UrlGenerator().getUrl() + service = Service.from({ + url: 'http://test.com' + api: ApiVersion.v2_0_0 + }) + test = -> new UrlGenerator().getUrl({}, service) should.Throw(test, Error, 'not a valid SDMX data, metadata or availability query') it 'throws an exception if the input is not a data or a metadata query', -> - test = -> new UrlGenerator().getUrl({test: 'Test'}) + service = Service.from({ + url: 'http://test.com' + api: ApiVersion.v2_0_0 + }) + test = -> new UrlGenerator().getUrl({test: 'Test'}, service) should.Throw(test, TypeError, 'not a valid SDMX data, metadata or availability query') From 08041775f6d3311207bc5302362805361149ad97 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 15:26:09 +0200 Subject: [PATCH 09/47] Simplify version checks --- src/utils/url-generator-data.coffee | 57 +++++++++++++---------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/utils/url-generator-data.coffee b/src/utils/url-generator-data.coffee index cc75e5a..6e7796f 100644 --- a/src/utils/url-generator-data.coffee +++ b/src/utils/url-generator-data.coffee @@ -13,21 +13,19 @@ translateDetail = (detail) -> else "attributes=dsd&measures=all" -createV1DataUrl = (query, service) -> - url = createEntryPoint service - url += "data/#{query.flow}/#{query.key}/#{query.provider}?" - if query.obsDimension - url += "dimensionAtObservation=#{query.obsDimension}&" - url += "detail=#{query.detail}" - if (service.api isnt ApiVersion.v1_0_0 and - service.api isnt ApiVersion.v1_0_1 and - service.api isnt ApiVersion.v1_0_2) - url += "&includeHistory=#{query.history}" - url += "&startPeriod=#{query.start}" if query.start - url += "&endPeriod=#{query.end}" if query.end - url += "&updatedAfter=#{query.updatedAfter}" if query.updatedAfter - url += "&firstNObservations=#{query.firstNObs}" if query.firstNObs - url += "&lastNObservations=#{query.lastNObs}" if query.lastNObs +createV1DataUrl = (q, s, a) -> + url = createEntryPoint s + url += "data/#{q.flow}/#{q.key}/#{q.provider}?" + if q.obsDimension + url += "dimensionAtObservation=#{q.obsDimension}&" + url += "detail=#{q.detail}" + if a >= ApiNumber.v1_1_0 + url += "&includeHistory=#{q.history}" + url += "&startPeriod=#{q.start}" if q.start + url += "&endPeriod=#{q.end}" if q.end + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url += "&firstNObservations=#{q.firstNObs}" if q.firstNObs + url += "&lastNObservations=#{q.lastNObs}" if q.lastNObs url createV2DataUrl = (q, s) -> @@ -47,7 +45,7 @@ createV2DataUrl = (q, s) -> createDataQuery = (q, s, a) -> if a < ApiNumber.v2_0_0 - createV1DataUrl q, s + createV1DataUrl q, s, a else createV2DataUrl q, s @@ -62,17 +60,14 @@ handleData2PathParams = (q) -> path.push q.key if q.key isnt 'all' or path.length if path.length then '/' + path.reverse().join('/') else '' -hasHistory = (q, s) -> - if (s.api isnt ApiVersion.v1_0_0 and - s.api isnt ApiVersion.v1_0_1 and - s.api isnt ApiVersion.v1_0_2 and - q.history) then true else false +hasHistory = (q, s, a) -> + if (a >= ApiNumber.v1_1_0 and q.history) then true else false -handleDataQueryParams = (q, s) -> +handleDataQueryParams = (q, s, a) -> p = [] p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension p.push "detail=#{q.detail}" unless q.detail is 'full' - p.push "includeHistory=#{q.history}" if hasHistory(q, s) + p.push "includeHistory=#{q.history}" if hasHistory(q, s, a) p.push "startPeriod=#{q.start}" if q.start p.push "endPeriod=#{q.end}" if q.end p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter @@ -80,24 +75,24 @@ handleDataQueryParams = (q, s) -> p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' -handleData2QueryParams = (q, s) -> +handleData2QueryParams = (q, s, a) -> p = [] p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension p.push "#{translateDetail q.detail}" unless q.detail is 'full' - p.push "includeHistory=#{q.history}" if hasHistory(q, s) + p.push "includeHistory=#{q.history}" if hasHistory(q, s, a) p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' -createShortV1Url = (q, s) -> +createShortV1Url = (q, s, a) -> u = createEntryPoint s u += "data/#{q.flow}" u += handleDataPathParams(q) - u += handleDataQueryParams(q, s) + u += handleDataQueryParams(q, s, a) u -createShortV2Url = (q, s) -> +createShortV2Url = (q, s, a) -> validateDataForV2 q, s u = createEntryPoint s fc = parseFlow q.flow @@ -106,14 +101,14 @@ createShortV2Url = (q, s) -> if fc[2] isnt "*" or pp isnt '' u += "/#{fc[2]}" u += pp - u += handleData2QueryParams(q, s) + u += handleData2QueryParams(q, s, a) u createShortDataQuery = (q, s, a) -> if a < ApiNumber.v2_0_0 - createShortV1Url q, s + createShortV1Url q, s, a else - createShortV2Url q, s + createShortV2Url q, s, a handler = class Handler From 6a0ae1e70e3c3a1eebc1ea5a2629bee62f2e168b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 15:33:49 +0200 Subject: [PATCH 10/47] Remove unreachable code --- src/utils/url-generator-common.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee index 6b407e5..23d4ac6 100644 --- a/src/utils/url-generator-common.coffee +++ b/src/utils/url-generator-common.coffee @@ -2,7 +2,6 @@ {VersionNumber} = require '../utils/sdmx-patterns' createEntryPoint = (s) -> - throw ReferenceError "#{s.url} is not a valid service" unless s.url url = s.url url = s.url + '/' unless s.url.endsWith('/') url From 404ccd1e7332dfda707dbe3a1ff68a49369867d4 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 15:34:09 +0200 Subject: [PATCH 11/47] Improve query validation --- src/utils/url-generator.coffee | 12 ++++++------ test/utils/url-generator.test.coffee | 11 +++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index eb01a11..66a3473 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -7,18 +7,18 @@ generator = class Generator getUrl: (@query, @service, skipDefaults) -> + throw ReferenceError "A valid query must be supplied" unless @query throw ReferenceError "#{@service} is not a valid service"\ unless @service and @service.url - if @query?.mode? + if @query.mode? new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) - else if @query?.flow? + else if @query.flow? new DataQueryHandler().handle(@query, @service, skipDefaults) - else if @query?.resource? + else if @query.resource? new MetadataQueryHandler().handle(@query, @service, skipDefaults) - else if @query?.context? + else if @query.context? new SchemaQueryHandler().handle(@query, @service, skipDefaults) else - throw TypeError "#{@query} is not a valid SDMX data, metadata or \ - availability query" + throw TypeError "#{@query} is not a valid query" exports.UrlGenerator = generator diff --git a/test/utils/url-generator.test.coffee b/test/utils/url-generator.test.coffee index 57416cb..b7e3496 100644 --- a/test/utils/url-generator.test.coffee +++ b/test/utils/url-generator.test.coffee @@ -11,13 +11,9 @@ should = require('chai').should() describe 'URL Generator (generic)', -> it 'throws an exception if no query is supplied', -> - service = Service.from({ - url: 'http://test.com' - api: ApiVersion.v2_0_0 - }) - test = -> new UrlGenerator().getUrl({}, service) + test = -> new UrlGenerator().getUrl() should.Throw(test, Error, - 'not a valid SDMX data, metadata or availability query') + 'A valid query must be supplied') it 'throws an exception if the input is not a data or a metadata query', -> service = Service.from({ @@ -25,8 +21,7 @@ describe 'URL Generator (generic)', -> api: ApiVersion.v2_0_0 }) test = -> new UrlGenerator().getUrl({test: 'Test'}, service) - should.Throw(test, TypeError, - 'not a valid SDMX data, metadata or availability query') + should.Throw(test, TypeError, 'not a valid query') it 'throws an exception if no service is supplied', -> query = MetadataQuery.from({ From b3a762ee77c8e4205c4840874b66bf88f1331c22 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 29 Jul 2022 15:35:39 +0200 Subject: [PATCH 12/47] Apply coding conventions --- src/utils/api-version.coffee | 2 +- src/utils/url-generator-availability.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/api-version.coffee b/src/utils/api-version.coffee index 1fccb14..3e4a79e 100644 --- a/src/utils/api-version.coffee +++ b/src/utils/api-version.coffee @@ -157,7 +157,7 @@ resources = # The set of valid resources for the latest API version. LATEST: resourcesV6 -numbers = +numbers = v1_0_0: 0 v1_0_1: 1 v1_0_2: 2 diff --git a/src/utils/url-generator-availability.coffee b/src/utils/url-generator-availability.coffee index 70dad8f..f458aa4 100644 --- a/src/utils/url-generator-availability.coffee +++ b/src/utils/url-generator-availability.coffee @@ -1,5 +1,6 @@ {ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' -{createEntryPoint, validateDataForV2, parseFlow} = require '../utils/url-generator-common' +{createEntryPoint, validateDataForV2, parseFlow} = + require '../utils/url-generator-common' handleAvailabilityPathParams = (q) -> path = [] From 7a0333eec8e6f6a53d688cb01b73cf86d10c6958 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 10:00:34 +0200 Subject: [PATCH 13/47] Remove params that are no longer used in 3.0 --- src/data/data-query2.coffee | 63 ++++++++++++ test/data/data-query2.test.coffee | 156 ++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 src/data/data-query2.coffee create mode 100644 test/data/data-query2.test.coffee diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee new file mode 100644 index 0000000..a65198d --- /dev/null +++ b/src/data/data-query2.coffee @@ -0,0 +1,63 @@ +{DataDetail} = require './data-detail' +{FlowRefType, SeriesKeyType, MultipleProviderRefType, NCNameIDType} = + require '../utils/sdmx-patterns' +{isValidEnum, isValidPattern, isValidPeriod, isValidDate, createErrorMessage} = + require '../utils/validators' + +defaults = + key: 'all' + history: false + +isValidHistory = (input, errors) -> + valid = typeof input is 'boolean' + errors.push "#{input} is not a valid value for history. Must be true or \ + false" unless valid + valid + +isValidNObs = (input, name, errors) -> + valid = typeof input is 'number' and input > 0 + errors.push "#{input} is not a valid value for #{name}. Must be a positive \ + integer" unless valid + valid + +ValidQuery = + flow: (i, e) -> isValidPattern(i, FlowRefType, 'flows', e) + key: (i, e) -> isValidPattern(i, SeriesKeyType, 'series key', e) + updatedAfter: (i, e) -> not i or isValidDate(i, 'updatedAfter', e) + firstNObs: (i, e) -> not i or isValidNObs(i, 'firstNObs', e) + lastNObs: (i, e) -> not i or isValidNObs(i, 'lastNObs', e) + obsDimension: (i, e) -> + not i or isValidPattern(i, NCNameIDType, 'obs dimension', e) + history: (i, e) -> isValidHistory(i, e) + +isValidQuery = (q) -> + errors = [] + isValid = false + for own k, v of q + isValid = ValidQuery[k](v, errors) + break unless isValid + {isValid: isValid, errors: errors} + +toKeyString = (dims) -> + ((if Array.isArray d then d.join('+') else d ? '') for d in dims).join('.') + +# A query for data, as defined by the SDMX RESTful API. +query = class DataQuery + + @from: (opts) -> + key = opts?.key ? defaults.key + key = toKeyString key if Array.isArray key + query = + flow: opts?.flow + key: key + updatedAfter: opts?.updatedAfter + firstNObs: opts?.firstNObs + lastNObs: opts?.lastNObs + obsDimension: opts?.obsDimension + history: opts?.history ? defaults.history + input = isValidQuery query + throw Error createErrorMessage(input.errors, 'data query') \ + unless input.isValid + query + +exports.DataQuery2 = query diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee new file mode 100644 index 0000000..f005b45 --- /dev/null +++ b/test/data/data-query2.test.coffee @@ -0,0 +1,156 @@ +should = require('chai').should() + +{DataDetail} = require '../../src/data/data-detail' +{DataQuery2} = require '../../src/data/data-query2' + +describe 'Data queries', -> + + it 'has the expected properties', -> + q = DataQuery2.from {flow: 'ICP'} + q.should.be.an 'object' + q.should.have.property 'flow' + q.should.have.property 'key' + q.should.have.property 'updatedAfter' + q.should.have.property 'firstNObs' + q.should.have.property 'lastNObs' + q.should.have.property 'obsDimension' + q.should.have.property 'history' + + it 'has the expected defaults', -> + flow = 'EXR' + q = DataQuery2.from {flow: flow} + q.should.have.property('flow').that.equals flow + q.should.have.property('key').that.equals 'all' + q.should.have.property('updatedAfter').that.is.undefined + q.should.have.property('firstNObs').that.is.undefined + q.should.have.property('lastNObs').that.is.undefined + q.should.have.property('obsDimension').that.is.undefined + q.should.have.property('history').that.is.false + + describe 'when setting the flow', -> + + it 'throws an exception when the flow is not set', -> + test = -> DataQuery2.from({flow: ' '}) + should.Throw(test, Error, 'Not a valid data query') + + test = -> DataQuery2.from({flow: undefined}) + should.Throw(test, Error, 'Not a valid data query') + + it 'throws an exception when the flow is invalid', -> + test = -> DataQuery2.from({flow: '1%'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the key', -> + + it 'a string representing the key can be used', -> + flow = 'EXR' + key = '.CHF+NOK.EUR..2' + q = DataQuery2.from({flow: flow, key: key}) + q.should.have.property('flow').that.equals flow + q.should.have.property('key').that.equals key + + it 'an array of arrays can be used to build the key', -> + values = [ + ['D'] + ['NOK', 'RUB', 'CHF'] + ['EUR'] + [] + ['A'] + ] + query = DataQuery2.from({flow: 'EXR', key: values}) + query.should.have.property('flow').that.equals 'EXR' + query.should.have.property('key').that.equals 'D.NOK+RUB+CHF.EUR..A' + + it 'a mixed array can be used to build the key', -> + values = [ + 'D' + ['NOK', 'RUB', 'CHF'] + '' + 'SP00' + undefined + ] + query = DataQuery2.from({flow: 'EXR', key: values}) + query.should.have.property('flow').that.equals 'EXR' + query.should.have.property('key').that.equals 'D.NOK+RUB+CHF..SP00.' + + it 'an exotic array can be used to build the key', -> + values = [ + '' + ['NOK', 'RUB', 'CHF'] + ['EUR'] + undefined + null + ] + query = DataQuery2.from({flow: 'EXR', key: values}) + query.should.have.property('flow').that.equals 'EXR' + query.should.have.property('key').that.equals '.NOK+RUB+CHF.EUR..' + + it 'throws an exception if the value for the key is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', key: '1%'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the updatedAfter timestamp', -> + + it 'a string representing a timestamp can be passed', -> + flow = 'EXR' + last = '2016-03-04T09:57:00Z' + q = DataQuery2.from({flow: flow, updatedAfter: last}) + q.should.have.property('flow').that.equals flow + q.should.have.property('updatedAfter').that.equals last + + it 'throws an exception if the value for updatedAfter is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', updatedAfter: 'now'}) + should.Throw(test, Error, 'Not a valid data query') + + test = -> DataQuery2.from({flow: 'EXR', updatedAfter: '2000-Q1'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the first and last number of observations', -> + + it 'integers representing the desired number of obs can be passed', -> + flow = 'EXR' + firstN = 1 + lastN = 3 + q = DataQuery2.from({flow: flow, firstNObs: firstN, lastNObs: lastN}) + q.should.have.property('flow').that.equals flow + q.should.have.property('firstNObs').that.equals firstN + q.should.have.property('lastNObs').that.equals lastN + + it 'throws an exception if the value for firstObs is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', firstNObs: -2}) + should.Throw(test, Error, 'Not a valid data query') + + test = -> DataQuery2.from({flow: 'EXR', firstNObs: 'test'}) + should.Throw(test, Error, 'Not a valid data query') + + it 'throws an exception if the value for lastNObs is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', lastNObs: -2}) + should.Throw(test, Error, 'Not a valid data query') + + test = -> DataQuery2.from({flow: 'EXR', lastNObs: 'test'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the dimension at observation level', -> + + it 'a string representing the dimension at the obs level can be passed', -> + flow = 'ECB,EXR,latest' + dim = 'CURRENCY' + q = DataQuery2.from({flow: flow, obsDimension: dim}) + q.should.have.property('flow').that.equals flow + q.should.have.property('obsDimension').that.equals dim + + it 'throws an exception if value for obs dimension is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', obsDimension: '*&^%$#@!)'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting whether historical data should be returned', -> + + it 'a boolean can be passed', -> + flow = 'ECB,EXR' + q = DataQuery2.from({flow: flow, history: true}) + q.should.have.property('flow').that.equals flow + q.should.have.property('history').that.is.true + + it 'throws an exception if the value for history is not a boolean', -> + test = -> DataQuery2.from({flow: 'EXR', history: 'test'}) + should.Throw(test, Error, 'Not a valid data query') From d2c7de48f2f45fee8eebfa20d3da075dd3dba255 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 10:47:05 +0200 Subject: [PATCH 14/47] Add support for attributes and measures --- src/data/data-query2.coffee | 19 +++++++++++++ test/data/data-query2.test.coffee | 44 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index a65198d..7b75216 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -7,6 +7,8 @@ defaults = key: 'all' history: false + attributes: 'dsd' + measures: 'all' isValidHistory = (input, errors) -> valid = typeof input is 'boolean' @@ -20,6 +22,17 @@ isValidNObs = (input, name, errors) -> integer" unless valid valid +isValidComp = (input, name, errors) -> + valid = true + if input.indexOf(",") > -1 + for i in input.split "," + r = isValidPattern(i, NCNameIDType, name, errors) + valid = false unless r + else + r = isValidPattern(input, NCNameIDType, name, errors) + valid = false unless r + valid + ValidQuery = flow: (i, e) -> isValidPattern(i, FlowRefType, 'flows', e) key: (i, e) -> isValidPattern(i, SeriesKeyType, 'series key', e) @@ -29,6 +42,8 @@ ValidQuery = obsDimension: (i, e) -> not i or isValidPattern(i, NCNameIDType, 'obs dimension', e) history: (i, e) -> isValidHistory(i, e) + attributes: (i, e) -> isValidComp(i, 'attributes', e) + measures: (i, e) -> isValidComp(i, 'measures', e) isValidQuery = (q) -> errors = [] @@ -47,6 +62,8 @@ query = class DataQuery @from: (opts) -> key = opts?.key ? defaults.key key = toKeyString key if Array.isArray key + attrs = opts?.attributes ? defaults.attributes + measures = opts?.measures ? defaults.measures query = flow: opts?.flow key: key @@ -55,6 +72,8 @@ query = class DataQuery lastNObs: opts?.lastNObs obsDimension: opts?.obsDimension history: opts?.history ? defaults.history + attributes: attrs + measures: measures input = isValidQuery query throw Error createErrorMessage(input.errors, 'data query') \ unless input.isValid diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index f005b45..3942ea9 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -15,6 +15,8 @@ describe 'Data queries', -> q.should.have.property 'lastNObs' q.should.have.property 'obsDimension' q.should.have.property 'history' + q.should.have.property 'attributes' + q.should.have.property 'measures' it 'has the expected defaults', -> flow = 'EXR' @@ -26,6 +28,8 @@ describe 'Data queries', -> q.should.have.property('lastNObs').that.is.undefined q.should.have.property('obsDimension').that.is.undefined q.should.have.property('history').that.is.false + q.should.have.property('attributes').that.equals 'dsd' + q.should.have.property('measures').that.equals 'all' describe 'when setting the flow', -> @@ -154,3 +158,43 @@ describe 'Data queries', -> it 'throws an exception if the value for history is not a boolean', -> test = -> DataQuery2.from({flow: 'EXR', history: 'test'}) should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the attributes to be returned', -> + + it 'a string representing one set of attributes can be passed', -> + flow = 'ECB,EXR,latest' + attrs = 'msd' + q = DataQuery2.from({flow: flow, attributes: attrs}) + q.should.have.property('flow').that.equals flow + q.should.have.property('attributes').that.equals attrs + + it 'a string representing multiple sets of attributes can be passed', -> + flow = 'ECB,EXR,latest' + attrs = 'msd,dataset,UNIT' + q = DataQuery2.from({flow: flow, attributes: attrs}) + q.should.have.property('flow').that.equals flow + q.should.have.property('attributes').that.equals attrs + + it 'throws an exception if value for attributes is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', attributes: '&1'}) + should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the measures to be returned', -> + + it 'a string representing one predefined set of measures can be passed', -> + flow = 'ECB,EXR,latest' + m = 'all' + q = DataQuery2.from({flow: flow, measures: m}) + q.should.have.property('flow').that.equals flow + q.should.have.property('measures').that.equals m + + it 'a string representing multiple measures can be passed', -> + flow = 'ECB,EXR,latest' + m = 'TURNOVER,OPEN_INTEREST' + q = DataQuery2.from({flow: flow, measures: m}) + q.should.have.property('flow').that.equals flow + q.should.have.property('measures').that.equals m + + it 'throws an exception if value for measures is invalid', -> + test = -> DataQuery2.from({flow: 'EXR', measures: '&1'}) + should.Throw(test, Error, 'Not a valid data query') From eb421bda21d104fc051b8562c6038be2a1513768 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 11:26:12 +0200 Subject: [PATCH 15/47] Add support for SDMX 3 key --- src/data/data-query2.coffee | 19 +++++++---- src/utils/sdmx-patterns.coffee | 9 ++++++ test/data/data-query2.test.coffee | 52 +++++++++---------------------- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index 7b75216..a55762a 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -1,5 +1,5 @@ {DataDetail} = require './data-detail' -{FlowRefType, SeriesKeyType, MultipleProviderRefType, NCNameIDType} = +{FlowRefType, Sdmx3SeriesKeyType, NCNameIDType} = require '../utils/sdmx-patterns' {isValidEnum, isValidPattern, isValidPeriod, isValidDate, createErrorMessage} = require '../utils/validators' @@ -33,9 +33,20 @@ isValidComp = (input, name, errors) -> valid = false unless r valid +isValidKey = (input, name, errors) -> + valid = true + if input.indexOf(",") > -1 + for i in input.split "," + r = isValidPattern(i, Sdmx3SeriesKeyType, name, errors) + valid = false unless r + else + r = isValidPattern(input, Sdmx3SeriesKeyType, name, errors) + valid = false unless r + valid + ValidQuery = flow: (i, e) -> isValidPattern(i, FlowRefType, 'flows', e) - key: (i, e) -> isValidPattern(i, SeriesKeyType, 'series key', e) + key: (i, e) -> isValidKey(i, 'series key', e) updatedAfter: (i, e) -> not i or isValidDate(i, 'updatedAfter', e) firstNObs: (i, e) -> not i or isValidNObs(i, 'firstNObs', e) lastNObs: (i, e) -> not i or isValidNObs(i, 'lastNObs', e) @@ -53,15 +64,11 @@ isValidQuery = (q) -> break unless isValid {isValid: isValid, errors: errors} -toKeyString = (dims) -> - ((if Array.isArray d then d.join('+') else d ? '') for d in dims).join('.') - # A query for data, as defined by the SDMX RESTful API. query = class DataQuery @from: (opts) -> key = opts?.key ? defaults.key - key = toKeyString key if Array.isArray key attrs = opts?.attributes ? defaults.attributes measures = opts?.measures ? defaults.measures query = diff --git a/src/utils/sdmx-patterns.coffee b/src/utils/sdmx-patterns.coffee index 781bb60..20eb8f3 100644 --- a/src/utils/sdmx-patterns.coffee +++ b/src/utils/sdmx-patterns.coffee @@ -90,6 +90,14 @@ SeriesKeyType = /// ^ )* $ /// +Sdmx3SeriesKeyType = /// ^ + (\* | #{IDType.source})? # One star or a dimension value + ( + [.] # Potentially followed by a dot + (\* | #{IDType.source})? # and repeating above pattern + )* +$ /// + FlowRefType = /// ^ ( #{IDType.source} @@ -154,6 +162,7 @@ exports.MultipleProviderRefType = MultipleProviderRefType exports.AgenciesRefType = MultipleAgenciesRefType exports.ReportingPeriodType = ReportingPeriodType exports.SeriesKeyType = SeriesKeyType +exports.Sdmx3SeriesKeyType = Sdmx3SeriesKeyType exports.MultipleIDType = MultipleIDType exports.MultipleVersionsType = MultipleVersionsType exports.MultipleNestedIDType = MultipleNestedIDType diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 3942ea9..0a666f9 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -48,49 +48,27 @@ describe 'Data queries', -> it 'a string representing the key can be used', -> flow = 'EXR' - key = '.CHF+NOK.EUR..2' + key = 'M.CHF.EUR.SP00.A' q = DataQuery2.from({flow: flow, key: key}) q.should.have.property('flow').that.equals flow q.should.have.property('key').that.equals key - it 'an array of arrays can be used to build the key', -> - values = [ - ['D'] - ['NOK', 'RUB', 'CHF'] - ['EUR'] - [] - ['A'] - ] - query = DataQuery2.from({flow: 'EXR', key: values}) - query.should.have.property('flow').that.equals 'EXR' - query.should.have.property('key').that.equals 'D.NOK+RUB+CHF.EUR..A' - - it 'a mixed array can be used to build the key', -> - values = [ - 'D' - ['NOK', 'RUB', 'CHF'] - '' - 'SP00' - undefined - ] - query = DataQuery2.from({flow: 'EXR', key: values}) - query.should.have.property('flow').that.equals 'EXR' - query.should.have.property('key').that.equals 'D.NOK+RUB+CHF..SP00.' - - it 'an exotic array can be used to build the key', -> - values = [ - '' - ['NOK', 'RUB', 'CHF'] - ['EUR'] - undefined - null - ] - query = DataQuery2.from({flow: 'EXR', key: values}) - query.should.have.property('flow').that.equals 'EXR' - query.should.have.property('key').that.equals '.NOK+RUB+CHF.EUR..' + it 'a string with wildcarded values can be used', -> + flow = 'EXR' + key = 'M.*.EUR.SP00.*' + q = DataQuery2.from({flow: flow, key: key}) + q.should.have.property('flow').that.equals flow + q.should.have.property('key').that.equals key + + it 'a string with multiple keys can be used', -> + flow = 'EXR' + key = 'M.CHF.EUR.SP00.A,D.CHF.EUR.SP00.A' + q = DataQuery2.from({flow: flow, key: key}) + q.should.have.property('flow').that.equals flow + q.should.have.property('key').that.equals key it 'throws an exception if the value for the key is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', key: '1%'}) + test = -> DataQuery2.from({flow: 'EXR', key: 'M.CHF+NOK.EUR..'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the updatedAfter timestamp', -> From c2efa962347121e3280a5733469b19ef58ce04c7 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 11:30:20 +0200 Subject: [PATCH 16/47] Remove unnecessary imports --- src/data/data-query2.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index a55762a..bbd5c13 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -1,7 +1,6 @@ -{DataDetail} = require './data-detail' {FlowRefType, Sdmx3SeriesKeyType, NCNameIDType} = require '../utils/sdmx-patterns' -{isValidEnum, isValidPattern, isValidPeriod, isValidDate, createErrorMessage} = +{isValidPattern, isValidDate, createErrorMessage} = require '../utils/validators' defaults = From 09927ec3589c99e1aade93d23a4c029474493761 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 11:32:23 +0200 Subject: [PATCH 17/47] Change default for key --- src/data/data-query2.coffee | 2 +- test/data/data-query2.test.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index bbd5c13..f1fd027 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -4,7 +4,7 @@ require '../utils/validators' defaults = - key: 'all' + key: '*' history: false attributes: 'dsd' measures: 'all' diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 0a666f9..4ff361c 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -22,7 +22,7 @@ describe 'Data queries', -> flow = 'EXR' q = DataQuery2.from {flow: flow} q.should.have.property('flow').that.equals flow - q.should.have.property('key').that.equals 'all' + q.should.have.property('key').that.equals '*' q.should.have.property('updatedAfter').that.is.undefined q.should.have.property('firstNObs').that.is.undefined q.should.have.property('lastNObs').that.is.undefined From 099d00b7790a303d63fb29ae22b87f1c044dbce9 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 12:24:52 +0200 Subject: [PATCH 18/47] Add support for new context in data queries --- src/data/data-query2.coffee | 8 ++- src/utils/sdmx-patterns.coffee | 20 ++++++ test/data/data-query2.test.coffee | 107 ++++++++++++++---------------- 3 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index f1fd027..4b6fd90 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -1,9 +1,10 @@ -{FlowRefType, Sdmx3SeriesKeyType, NCNameIDType} = +{ContextRefType, Sdmx3SeriesKeyType, NCNameIDType} = require '../utils/sdmx-patterns' {isValidPattern, isValidDate, createErrorMessage} = require '../utils/validators' defaults = + context: '*=*:*(*)' key: '*' history: false attributes: 'dsd' @@ -44,7 +45,7 @@ isValidKey = (input, name, errors) -> valid ValidQuery = - flow: (i, e) -> isValidPattern(i, FlowRefType, 'flows', e) + context: (i, e) -> isValidPattern(i, ContextRefType, 'context', e) key: (i, e) -> isValidKey(i, 'series key', e) updatedAfter: (i, e) -> not i or isValidDate(i, 'updatedAfter', e) firstNObs: (i, e) -> not i or isValidNObs(i, 'firstNObs', e) @@ -67,11 +68,12 @@ isValidQuery = (q) -> query = class DataQuery @from: (opts) -> + context = opts?.context ? defaults.context key = opts?.key ? defaults.key attrs = opts?.attributes ? defaults.attributes measures = opts?.measures ? defaults.measures query = - flow: opts?.flow + context: context key: key updatedAfter: opts?.updatedAfter firstNObs: opts?.firstNObs diff --git a/src/utils/sdmx-patterns.coffee b/src/utils/sdmx-patterns.coffee index 20eb8f3..0958a8f 100644 --- a/src/utils/sdmx-patterns.coffee +++ b/src/utils/sdmx-patterns.coffee @@ -109,6 +109,25 @@ FlowRefType = /// ^ ) $ /// +ContextType = /// + (datastructure|dataflow|provisionagreement) + /// + + +ContextRefType = /// ^ + ( + (#{ContextType.source} | \*) # The context + = # Then a separator + (#{NestedNCNameIDType.source} | \*) # Then the agency + : # Then a separator + (#{IDType.source} | \*) # Then the artefact ID + \( # Then an open parenthesis + (#{VersionNumber.source} | #{SemVer.source} | \*) # Then the version + \) # Then a closing parenthesis + ) + $ /// + + ProviderRefType = /// (#{NestedNCNameIDType.source},)? # May start with the agency owning the scheme #{IDType.source} # The id of the provider @@ -166,3 +185,4 @@ exports.Sdmx3SeriesKeyType = Sdmx3SeriesKeyType exports.MultipleIDType = MultipleIDType exports.MultipleVersionsType = MultipleVersionsType exports.MultipleNestedIDType = MultipleNestedIDType +exports.ContextRefType = ContextRefType diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 4ff361c..b3f97c7 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -6,9 +6,9 @@ should = require('chai').should() describe 'Data queries', -> it 'has the expected properties', -> - q = DataQuery2.from {flow: 'ICP'} + q = DataQuery2.from {} q.should.be.an 'object' - q.should.have.property 'flow' + q.should.have.property 'context' q.should.have.property 'key' q.should.have.property 'updatedAfter' q.should.have.property 'firstNObs' @@ -19,9 +19,8 @@ describe 'Data queries', -> q.should.have.property 'measures' it 'has the expected defaults', -> - flow = 'EXR' - q = DataQuery2.from {flow: flow} - q.should.have.property('flow').that.equals flow + q = DataQuery2.from {} + q.should.have.property('context').that.equals '*=*:*(*)' q.should.have.property('key').that.equals '*' q.should.have.property('updatedAfter').that.is.undefined q.should.have.property('firstNObs').that.is.undefined @@ -31,148 +30,142 @@ describe 'Data queries', -> q.should.have.property('attributes').that.equals 'dsd' q.should.have.property('measures').that.equals 'all' - describe 'when setting the flow', -> + describe 'when setting the context', -> - it 'throws an exception when the flow is not set', -> - test = -> DataQuery2.from({flow: ' '}) + it 'throws an exception when the context is invalid', -> + test = -> DataQuery2.from({context: '1%'}) should.Throw(test, Error, 'Not a valid data query') - test = -> DataQuery2.from({flow: undefined}) - should.Throw(test, Error, 'Not a valid data query') + it 'accepts the usual context types', -> + c1 = 'datastructure=BIS:BIS_CBS(1.0)' + q1 = DataQuery2.from({context: c1}) + q1.should.have.property('context').that.equals c1 - it 'throws an exception when the flow is invalid', -> - test = -> DataQuery2.from({flow: '1%'}) - should.Throw(test, Error, 'Not a valid data query') + c2 = 'dataflow=BIS:CBS(1.0)' + q2 = DataQuery2.from({context: c2}) + q2.should.have.property('context').that.equals c2 + + c3 = 'provisionagreement=BIS:CBS_5B0(1.0)' + q3 = DataQuery2.from({context: c3}) + q3.should.have.property('context').that.equals c3 + + it 'accepts the new wildcard types', -> + c = 'dataflow=BIS:*(~)' + q = DataQuery2.from({context: c}) + q.should.have.property('context').that.equals c + + # it 'accepts multiple values', -> + # c = 'dataflow=BIS:BIS_CBS,BIS_LBS(*)' + # q = DataQuery2.from({context: c}) + # q.should.have.property('context').that.equals c describe 'when setting the key', -> it 'a string representing the key can be used', -> - flow = 'EXR' key = 'M.CHF.EUR.SP00.A' - q = DataQuery2.from({flow: flow, key: key}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) q.should.have.property('key').that.equals key it 'a string with wildcarded values can be used', -> - flow = 'EXR' key = 'M.*.EUR.SP00.*' - q = DataQuery2.from({flow: flow, key: key}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) q.should.have.property('key').that.equals key it 'a string with multiple keys can be used', -> - flow = 'EXR' key = 'M.CHF.EUR.SP00.A,D.CHF.EUR.SP00.A' - q = DataQuery2.from({flow: flow, key: key}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) q.should.have.property('key').that.equals key it 'throws an exception if the value for the key is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', key: 'M.CHF+NOK.EUR..'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: 'M.CHF+NOK.EUR..'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the updatedAfter timestamp', -> it 'a string representing a timestamp can be passed', -> - flow = 'EXR' last = '2016-03-04T09:57:00Z' - q = DataQuery2.from({flow: flow, updatedAfter: last}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: last}) q.should.have.property('updatedAfter').that.equals last it 'throws an exception if the value for updatedAfter is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', updatedAfter: 'now'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: 'now'}) should.Throw(test, Error, 'Not a valid data query') - test = -> DataQuery2.from({flow: 'EXR', updatedAfter: '2000-Q1'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: '2000-Q1'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the first and last number of observations', -> it 'integers representing the desired number of obs can be passed', -> - flow = 'EXR' firstN = 1 lastN = 3 - q = DataQuery2.from({flow: flow, firstNObs: firstN, lastNObs: lastN}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', firstNObs: firstN, lastNObs: lastN}) q.should.have.property('firstNObs').that.equals firstN q.should.have.property('lastNObs').that.equals lastN it 'throws an exception if the value for firstObs is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', firstNObs: -2}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', firstNObs: -2}) should.Throw(test, Error, 'Not a valid data query') - test = -> DataQuery2.from({flow: 'EXR', firstNObs: 'test'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', firstNObs: 'test'}) should.Throw(test, Error, 'Not a valid data query') it 'throws an exception if the value for lastNObs is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', lastNObs: -2}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', lastNObs: -2}) should.Throw(test, Error, 'Not a valid data query') - test = -> DataQuery2.from({flow: 'EXR', lastNObs: 'test'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', lastNObs: 'test'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the dimension at observation level', -> it 'a string representing the dimension at the obs level can be passed', -> - flow = 'ECB,EXR,latest' dim = 'CURRENCY' - q = DataQuery2.from({flow: flow, obsDimension: dim}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', obsDimension: dim}) q.should.have.property('obsDimension').that.equals dim it 'throws an exception if value for obs dimension is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', obsDimension: '*&^%$#@!)'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', obsDimension: '*&^%$#@!)'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting whether historical data should be returned', -> it 'a boolean can be passed', -> - flow = 'ECB,EXR' - q = DataQuery2.from({flow: flow, history: true}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', history: true}) q.should.have.property('history').that.is.true it 'throws an exception if the value for history is not a boolean', -> - test = -> DataQuery2.from({flow: 'EXR', history: 'test'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', history: 'test'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the attributes to be returned', -> it 'a string representing one set of attributes can be passed', -> - flow = 'ECB,EXR,latest' attrs = 'msd' - q = DataQuery2.from({flow: flow, attributes: attrs}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', attributes: attrs}) q.should.have.property('attributes').that.equals attrs it 'a string representing multiple sets of attributes can be passed', -> - flow = 'ECB,EXR,latest' attrs = 'msd,dataset,UNIT' - q = DataQuery2.from({flow: flow, attributes: attrs}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', attributes: attrs}) q.should.have.property('attributes').that.equals attrs it 'throws an exception if value for attributes is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', attributes: '&1'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', attributes: '&1'}) should.Throw(test, Error, 'Not a valid data query') describe 'when setting the measures to be returned', -> it 'a string representing one predefined set of measures can be passed', -> - flow = 'ECB,EXR,latest' m = 'all' - q = DataQuery2.from({flow: flow, measures: m}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: m}) q.should.have.property('measures').that.equals m it 'a string representing multiple measures can be passed', -> - flow = 'ECB,EXR,latest' m = 'TURNOVER,OPEN_INTEREST' - q = DataQuery2.from({flow: flow, measures: m}) - q.should.have.property('flow').that.equals flow + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: m}) q.should.have.property('measures').that.equals m it 'throws an exception if value for measures is invalid', -> - test = -> DataQuery2.from({flow: 'EXR', measures: '&1'}) + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: '&1'}) should.Throw(test, Error, 'Not a valid data query') From 5179c817c72c882d5a6a076b527a4c391d7f025c Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 14:12:16 +0200 Subject: [PATCH 19/47] Add support for multiple values in context --- src/utils/sdmx-patterns.coffee | 62 ++++++++++++++++++++----------- test/data/data-query2.test.coffee | 8 ++-- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/utils/sdmx-patterns.coffee b/src/utils/sdmx-patterns.coffee index 0958a8f..215f75c 100644 --- a/src/utils/sdmx-patterns.coffee +++ b/src/utils/sdmx-patterns.coffee @@ -109,25 +109,6 @@ FlowRefType = /// ^ ) $ /// -ContextType = /// - (datastructure|dataflow|provisionagreement) - /// - - -ContextRefType = /// ^ - ( - (#{ContextType.source} | \*) # The context - = # Then a separator - (#{NestedNCNameIDType.source} | \*) # Then the agency - : # Then a separator - (#{IDType.source} | \*) # Then the artefact ID - \( # Then an open parenthesis - (#{VersionNumber.source} | #{SemVer.source} | \*) # Then the version - \) # Then a closing parenthesis - ) - $ /// - - ProviderRefType = /// (#{NestedNCNameIDType.source},)? # May start with the agency owning the scheme #{IDType.source} # The id of the provider @@ -139,18 +120,26 @@ MultipleProviderRefType = /// ^ Sdmx_3_0_all = ///\*/// -MultipleAgenciesRefType = /// ^ +MultipleAgencies = /// ( #{Sdmx_3_0_all.source} | #{NestedNCNameIDType.source}([+,]#{NestedNCNameIDType.source})* ) + /// + +MultipleAgenciesRefType = /// ^ + #{MultipleAgencies.source} $/// -MultipleIDType = /// ^ +MultipleIDs = /// ( #{Sdmx_3_0_all.source} | #{IDType.source}([+,]#{IDType.source})* ) + /// + +MultipleIDType = /// ^ + #{MultipleIDs.source} $/// MultipleNestedIDType = /// ^ @@ -160,6 +149,13 @@ MultipleNestedIDType = /// ^ ) $/// +MultipleVersions = /// + ( + #{Sdmx_3_0_all.source} + | #{VersionType.source}([,]#{VersionType.source})* + ) + /// + MultipleVersionsType = /// ^ #{VersionType.source}([+,]#{VersionType.source})* $/// @@ -168,6 +164,30 @@ ReportingPeriodType = /// ^ \d{4}-([ASTQ]\d{1}|[MW]\d{2}|[D]\d{3}) $ /// +ContextType = /// + (datastructure|dataflow|provisionagreement) + /// + +MultipleContextType = /// + ( + #{Sdmx_3_0_all.source} + | #{ContextType.source}([+,]#{ContextType.source})* + ) + /// + +ContextRefType = /// ^ + ( + #{MultipleContextType.source} # The context + = # Then the separator between context & agency + #{MultipleAgencies.source} # Then one or more agencies + : # Then the separator between agency & id + #{MultipleIDs.source} # Then one or more artefact IDs + \( # Then an open parenthesis + #{MultipleVersions.source} # Then one or more versions + \) # Then a closing parenthesis + ) + $ /// + exports.NCNameIDType = NCNameIDTypeAlone exports.NestedNCNameIDType = NestedNCNameIDTypeAlone exports.IDType = IDTypeAlone diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index b3f97c7..ff710af 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -54,10 +54,10 @@ describe 'Data queries', -> q = DataQuery2.from({context: c}) q.should.have.property('context').that.equals c - # it 'accepts multiple values', -> - # c = 'dataflow=BIS:BIS_CBS,BIS_LBS(*)' - # q = DataQuery2.from({context: c}) - # q.should.have.property('context').that.equals c + it 'accepts multiple values', -> + c = 'dataflow=BIS:BIS_CBS,BIS_LBS(*)' + q = DataQuery2.from({context: c}) + q.should.have.property('context').that.equals c describe 'when setting the key', -> From e25c5f876eeddcd0fd0a2eb1cde6417b0c245b48 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Tue, 2 Aug 2022 14:21:08 +0200 Subject: [PATCH 20/47] Improve code coverage --- test/data/data-query2.test.coffee | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index ff710af..103ea65 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -30,6 +30,18 @@ describe 'Data queries', -> q.should.have.property('attributes').that.equals 'dsd' q.should.have.property('measures').that.equals 'all' + it 'has the expected defaults, even when nothing gets passed', -> + q = DataQuery2.from null + q.should.have.property('context').that.equals '*=*:*(*)' + q.should.have.property('key').that.equals '*' + q.should.have.property('updatedAfter').that.is.undefined + q.should.have.property('firstNObs').that.is.undefined + q.should.have.property('lastNObs').that.is.undefined + q.should.have.property('obsDimension').that.is.undefined + q.should.have.property('history').that.is.false + q.should.have.property('attributes').that.equals 'dsd' + q.should.have.property('measures').that.equals 'all' + describe 'when setting the context', -> it 'throws an exception when the context is invalid', -> @@ -80,6 +92,10 @@ describe 'Data queries', -> test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: 'M.CHF+NOK.EUR..'}) should.Throw(test, Error, 'Not a valid data query') + it 'throws an exception if one of the values for the key is invalid', -> + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: 'M.CHF.EUR,M.USD+GBP.EUR'}) + should.Throw(test, Error, 'Not a valid data query') + describe 'when setting the updatedAfter timestamp', -> it 'a string representing a timestamp can be passed', -> @@ -154,6 +170,10 @@ describe 'Data queries', -> test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', attributes: '&1'}) should.Throw(test, Error, 'Not a valid data query') + it 'throws an exception if one of the values for attributes is invalid', -> + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', attributes: 'UNIT,&1'}) + should.Throw(test, Error, 'Not a valid data query') + describe 'when setting the measures to be returned', -> it 'a string representing one predefined set of measures can be passed', -> @@ -169,3 +189,7 @@ describe 'Data queries', -> it 'throws an exception if value for measures is invalid', -> test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: '&1'}) should.Throw(test, Error, 'Not a valid data query') + + it 'throws an exception if one of the values for measures is invalid', -> + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: 'TURNOVER,&1'}) + should.Throw(test, Error, 'Not a valid data query') From ad1f9e4c017f2a60c3c6694d1ed9c1e78b1c335e Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Wed, 3 Aug 2022 14:04:49 +0200 Subject: [PATCH 21/47] Add support for filters --- src/data/data-query2.coffee | 15 +++++++++++++- src/utils/sdmx-patterns.coffee | 19 ++++++++++++++++++ test/data/data-query2.test.coffee | 33 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index 4b6fd90..bb7785e 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -1,4 +1,4 @@ -{ContextRefType, Sdmx3SeriesKeyType, NCNameIDType} = +{ContextRefType, Sdmx3SeriesKeyType, NCNameIDType, FiltersType} = require '../utils/sdmx-patterns' {isValidPattern, isValidDate, createErrorMessage} = require '../utils/validators' @@ -9,6 +9,7 @@ defaults = history: false attributes: 'dsd' measures: 'all' + filters: [] isValidHistory = (input, errors) -> valid = typeof input is 'boolean' @@ -44,6 +45,14 @@ isValidKey = (input, name, errors) -> valid = false unless r valid +isValidFilters = (input, name, errors) -> + valid = true + for filter in input + r = isValidPattern(filter, FiltersType, name, errors) + valid = false unless r + valid + + ValidQuery = context: (i, e) -> isValidPattern(i, ContextRefType, 'context', e) key: (i, e) -> isValidKey(i, 'series key', e) @@ -55,6 +64,7 @@ ValidQuery = history: (i, e) -> isValidHistory(i, e) attributes: (i, e) -> isValidComp(i, 'attributes', e) measures: (i, e) -> isValidComp(i, 'measures', e) + filters: (i, e) -> isValidFilters(i, 'filters', e) isValidQuery = (q) -> errors = [] @@ -72,6 +82,8 @@ query = class DataQuery key = opts?.key ? defaults.key attrs = opts?.attributes ? defaults.attributes measures = opts?.measures ? defaults.measures + filters = opts?.filters ? defaults.filters + filters = [filters] if not Array.isArray filters query = context: context key: key @@ -82,6 +94,7 @@ query = class DataQuery history: opts?.history ? defaults.history attributes: attrs measures: measures + filters: filters input = isValidQuery query throw Error createErrorMessage(input.errors, 'data query') \ unless input.isValid diff --git a/src/utils/sdmx-patterns.coffee b/src/utils/sdmx-patterns.coffee index 215f75c..ec60866 100644 --- a/src/utils/sdmx-patterns.coffee +++ b/src/utils/sdmx-patterns.coffee @@ -188,6 +188,24 @@ ContextRefType = /// ^ ) $ /// +Operators = /// + (eq|ne|lt|le|gt|ge|co|nc|sw|ew) + /// + +FilterValue = /// + ( + (#{Operators.source}:)?#{IDType.source} + ) + /// + +FiltersType = /// ^ + ( + #{NCNameIDType.source} + = + #{FilterValue.source}([+,]#{FilterValue.source})* + ) + $ /// + exports.NCNameIDType = NCNameIDTypeAlone exports.NestedNCNameIDType = NestedNCNameIDTypeAlone exports.IDType = IDTypeAlone @@ -206,3 +224,4 @@ exports.MultipleIDType = MultipleIDType exports.MultipleVersionsType = MultipleVersionsType exports.MultipleNestedIDType = MultipleNestedIDType exports.ContextRefType = ContextRefType +exports.FiltersType = FiltersType diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 103ea65..bed22e2 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -17,6 +17,7 @@ describe 'Data queries', -> q.should.have.property 'history' q.should.have.property 'attributes' q.should.have.property 'measures' + q.should.have.property 'filters' it 'has the expected defaults', -> q = DataQuery2.from {} @@ -29,6 +30,8 @@ describe 'Data queries', -> q.should.have.property('history').that.is.false q.should.have.property('attributes').that.equals 'dsd' q.should.have.property('measures').that.equals 'all' + q.should.have.property('filters').that.is.instanceOf Array + q.should.have.property('filters').that.has.lengthOf 0 it 'has the expected defaults, even when nothing gets passed', -> q = DataQuery2.from null @@ -41,6 +44,8 @@ describe 'Data queries', -> q.should.have.property('history').that.is.false q.should.have.property('attributes').that.equals 'dsd' q.should.have.property('measures').that.equals 'all' + q.should.have.property('filters').that.has.lengthOf 0 + describe 'when setting the context', -> @@ -193,3 +198,31 @@ describe 'Data queries', -> it 'throws an exception if one of the values for measures is invalid', -> test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', measures: 'TURNOVER,&1'}) should.Throw(test, Error, 'Not a valid data query') + + describe 'when setting the filters to be applied', -> + + it 'a string representing one filter can be passed', -> + f = 'FREQ=A' + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: f}) + q.should.have.property('filters').that.has.lengthOf 1 + r = q.filters[0] + r.should.equal f + + it 'an array representing multiple filters can be passed', -> + f1 = 'FREQ=A' + f2 = 'TIME_PERIOD=ge:2020-01+le:2020-12,2022-08' + q = DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: [f1, f2]}) + q.should.have.property('filters').that.has.lengthOf 2 + r1 = q.filters[0] + r1.should.equal f1 + r2 = q.filters[1] + r2.should.equal f2 + + it 'throws an exception if the filter is invalid', -> + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: 'FREQ=badop:UNIT'}) + should.Throw(test, Error, 'Not a valid data query') + + it 'throws an exception if one of the filters is invalid', -> + test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: ['FREQ=A', '$1']}) + should.Throw(test, Error, 'Not a valid data query') + From cc852e9665b8f7cca3c1de801c4d1e76905dfce4 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 10:42:23 +0200 Subject: [PATCH 22/47] Generate URL for SDMX 3.0 queries --- src/utils/url-generator-common.coffee | 12 +- src/utils/url-generator-data2.coffee | 74 ++++++++++ src/utils/url-generator.coffee | 5 +- test/utils/url-generator-data2.test.coffee | 155 +++++++++++++++++++++ 4 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 src/utils/url-generator-data2.coffee create mode 100644 test/utils/url-generator-data2.test.coffee diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee index 23d4ac6..d626828 100644 --- a/src/utils/url-generator-common.coffee +++ b/src/utils/url-generator-common.coffee @@ -15,8 +15,15 @@ parseFlow = (f) -> else parts +contextPattern = /// + (.*)=(.*):(.*)\((.*)\) +/// + +parseContext = (f) -> f.match(contextPattern)[1..4] + + validateDataForV2 = (q, s) -> - if q.provider isnt "all" + if q.provider? and q.provider isnt "all" throw Error "provider not allowed in #{s.api}" if q.start throw Error "start not allowed in #{s.api}" @@ -39,4 +46,5 @@ exports.createEntryPoint = createEntryPoint exports.parseFlow = parseFlow exports.validateDataForV2 = validateDataForV2 exports.checkVersion = checkVersion -exports.checkMultipleItems = checkMultipleItems \ No newline at end of file +exports.checkMultipleItems = checkMultipleItems +exports.parseContext = parseContext \ No newline at end of file diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee new file mode 100644 index 0000000..52accda --- /dev/null +++ b/src/utils/url-generator-data2.coffee @@ -0,0 +1,74 @@ +{ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' +{createEntryPoint, validateDataForV2, parseContext} = + require '../utils/url-generator-common' + +filterPattern = /// + (.*)=(.*) +/// + +parseFilter = (f) -> f.match(filterPattern)[1..2] + +createDataQuery = (q, s, a) -> + validateDataForV2 q, s + url = createEntryPoint s + fc = parseContext q.context + url += "data/#{fc[0]}/#{fc[1]}/#{fc[2]}/#{fc[3]}/" + url += if q.key == "all" then "*?" else "#{q.key}?" + if q.filters + for filter in q.filters + f = parseFilter filter + url += "c[#{f[0]}]=#{f[1]}&" + if q.obsDimension + url += "dimensionAtObservation=#{q.obsDimension}&" + url += "attributes=#{q.attributes}" + url += "&measures=#{q.measures}" + url += "&includeHistory=#{q.history}" + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url += "&firstNObservations=#{q.firstNObs}" if q.firstNObs + url += "&lastNObservations=#{q.lastNObs}" if q.lastNObs + url + +handleDataPathParams = (q) -> + p = [] + c = parseContext q.context + p.push q.key if q.key isnt '*' or p.length + p.push c[3] if c[3] isnt '*' or p.length + p.push c[2] if c[2] isnt '*' or p.length + p.push c[1] if c[1] isnt '*' or p.length + p.push c[0] if c[0] isnt '*' or p.length + if p.length then '/' + p.reverse().join('/') else '' + +handleDataQueryParams = (q, s, a) -> + p = [] + if q.filters + for filter in q.filters + f = parseFilter filter + p.push "c[#{f[0]}]=#{f[1]}" + p.push "dimensionAtObservation=#{q.obsDimension}" if q.obsDimension + p.push "includeHistory=#{q.history}" if q.history + p.push "attributes=#{q.attributes}" if q.attributes isnt 'dsd' + p.push "measures=#{q.measures}" if q.measures isnt 'all' + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "firstNObservations=#{q.firstNObs}" if q.firstNObs + p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +createShortDataQuery = (q, s, a) -> + validateDataForV2 q, s + u = createEntryPoint s + u += 'data' + p = handleDataPathParams(q) + u += p + u += handleDataQueryParams(q, s, a) + u + +handler = class Handler + + handle: (q, s, skip) -> + api = ApiNumber[getKeyFromVersion(s.api)] + if skip + createShortDataQuery(q, s, api) + else + createDataQuery(q, s, api) + +exports.Data2QueryHandler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 66a3473..3444a30 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -2,6 +2,7 @@ {AvailabilityQueryHandler} = require '../utils/url-generator-availability' {SchemaQueryHandler} = require '../utils/url-generator-schema' {DataQueryHandler} = require '../utils/url-generator-data' +{Data2QueryHandler} = require '../utils/url-generator-data2' {MetadataQueryHandler} = require '../utils/url-generator-metadata' generator = class Generator @@ -10,7 +11,9 @@ generator = class Generator throw ReferenceError "A valid query must be supplied" unless @query throw ReferenceError "#{@service} is not a valid service"\ unless @service and @service.url - if @query.mode? + if @query.context? and @query.attributes? + new Data2QueryHandler().handle(@query, @service, skipDefaults) + else if @query.mode? new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) else if @query.flow? new DataQueryHandler().handle(@query, @service, skipDefaults) diff --git a/test/utils/url-generator-data2.test.coffee b/test/utils/url-generator-data2.test.coffee new file mode 100644 index 0000000..e661afb --- /dev/null +++ b/test/utils/url-generator-data2.test.coffee @@ -0,0 +1,155 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{DataQuery2} = require '../../src/data/data-query2' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for data queries', -> + + it 'generates a URL for a full data query', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A\ + ?c[OBS_CONF]=F&dimensionAtObservation=CURRENCY\ + &attributes=dataset,series&measures=none\ + &includeHistory=true\ + &updatedAfter=2016-03-01T00:00:00Z\ + &firstNObservations=1&lastNObservations=2" + query = DataQuery2.from({ + context: 'dataflow=*:EXR(*)' + key: 'A..EUR.SP00.A' + filters: 'OBS_CONF=F' + updatedAfter: '2016-03-01T00:00:00Z' + firstNObs: 1 + lastNObs: 2 + obsDimension: 'CURRENCY' + attributes: 'dataset,series' + measures: 'none' + history: true + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial data query', -> + expected = "http://test.com/data/dataflow/*/EXR/*/*?\ + attributes=dsd&measures=all&includeHistory=false" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'offers to skip all default values', -> + expected = "http://test.com/data" + query = DataQuery2.from null + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (context)', -> + expected = "http://test.com/data/dataflow" + query = DataQuery2.from({context: 'dataflow=*:*(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (agency)', -> + expected = "http://test.com/data/*/BIS" + query = DataQuery2.from({context: '*=BIS:*(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (artefact)', -> + expected = "http://test.com/data/*/*/EXR" + query = DataQuery2.from({context: '*=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (version)', -> + expected = "http://test.com/data/*/*/*/~" + query = DataQuery2.from({context: '*=*:*(~)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (key)', -> + expected = "http://test.com/data/*/*/*/*/A.CHF" + query = DataQuery2.from({key: 'A.CHF'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (filters)', -> + expected = "http://test.com/data?c[REF_AREA]=UY,AR" + query = DataQuery2.from({filters: 'REF_AREA=UY,AR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (updatedAfter)', -> + expected = "http://test.com/data/dataflow/*/EXR?updatedAfter=2016-03-01T00:00:00Z" + query = DataQuery2.from({ + context: 'dataflow=*:EXR(*)' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (firstNObs)', -> + expected = "http://test.com/data/dataflow/*/EXR?firstNObservations=1" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', firstNObs: 1}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (lastNObs)', -> + expected = "http://test.com/data/dataflow/*/EXR?lastNObservations=2" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', lastNObs: 2}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (obsDim)', -> + expected = "http://test.com/data/dataflow/*/EXR?dimensionAtObservation=CURR" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', obsDimension: 'CURR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (attributes)', -> + expected = "http://test.com/data/dataflow/*/EXR?attributes=msd" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', attributes: 'msd'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (measures)', -> + expected = "http://test.com/data/dataflow/*/EXR?measures=none" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', measures: 'none'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (history)', -> + expected = "http://test.com/data/dataflow/*/EXR?includeHistory=true" + query = DataQuery2.from({context: 'dataflow=*:EXR(*)', history: true}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (various)', -> + expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A?\ + updatedAfter=2016-03-01T00:00:00Z\ + &attributes=msd&dimensionAtObservation=CURRENCY" + query = DataQuery2.from({ + context: 'dataflow=*:EXR(*)' + key: 'A..EUR.SP00.A' + attributes: 'msd' + obsDimension: 'CURRENCY' + updatedAfter: '2016-03-01T00:00:00Z' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected From c3d11cb4784ae4db69988e8f2bf501227e50a10b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 10:45:14 +0200 Subject: [PATCH 23/47] Apply coding guidelines --- src/utils/sdmx-patterns.coffee | 6 +++--- src/utils/url-generator-data2.coffee | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/sdmx-patterns.coffee b/src/utils/sdmx-patterns.coffee index ec60866..c7383c1 100644 --- a/src/utils/sdmx-patterns.coffee +++ b/src/utils/sdmx-patterns.coffee @@ -131,7 +131,7 @@ MultipleAgenciesRefType = /// ^ #{MultipleAgencies.source} $/// -MultipleIDs = /// +MultipleIDs = /// ( #{Sdmx_3_0_all.source} | #{IDType.source}([+,]#{IDType.source})* @@ -182,7 +182,7 @@ ContextRefType = /// ^ #{MultipleAgencies.source} # Then one or more agencies : # Then the separator between agency & id #{MultipleIDs.source} # Then one or more artefact IDs - \( # Then an open parenthesis + \( # Then an open parenthesis #{MultipleVersions.source} # Then one or more versions \) # Then a closing parenthesis ) @@ -201,7 +201,7 @@ FilterValue = /// FiltersType = /// ^ ( #{NCNameIDType.source} - = + = #{FilterValue.source}([+,]#{FilterValue.source})* ) $ /// diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 52accda..4880070 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -35,7 +35,7 @@ handleDataPathParams = (q) -> p.push c[3] if c[3] isnt '*' or p.length p.push c[2] if c[2] isnt '*' or p.length p.push c[1] if c[1] isnt '*' or p.length - p.push c[0] if c[0] isnt '*' or p.length + p.push c[0] if c[0] isnt '*' or p.length if p.length then '/' + p.reverse().join('/') else '' handleDataQueryParams = (q, s, a) -> From 160154116574356226c485eb2ff1485ae2829a17 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 10:48:31 +0200 Subject: [PATCH 24/47] Remove wrong check for key=all --- src/utils/url-generator-data2.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 4880070..2bc4f54 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -13,7 +13,7 @@ createDataQuery = (q, s, a) -> url = createEntryPoint s fc = parseContext q.context url += "data/#{fc[0]}/#{fc[1]}/#{fc[2]}/#{fc[3]}/" - url += if q.key == "all" then "*?" else "#{q.key}?" + url += "#{q.key}?" if q.filters for filter in q.filters f = parseFilter filter From cdc4e7873def45cd6d7a7eb9a4718504e2d391e7 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 11:01:50 +0200 Subject: [PATCH 25/47] SDMX 3.0 queries require an SDMX 3.0 API --- src/utils/url-generator-data2.coffee | 2 ++ test/utils/url-generator-data2.test.coffee | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 2bc4f54..6561228 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -66,6 +66,8 @@ handler = class Handler handle: (q, s, skip) -> api = ApiNumber[getKeyFromVersion(s.api)] + if api < ApiNumber.v2_0_0 + throw Error "SDMX 3.0 queries not allowed in #{s.api}" if skip createShortDataQuery(q, s, api) else diff --git a/test/utils/url-generator-data2.test.coffee b/test/utils/url-generator-data2.test.coffee index e661afb..be705ca 100644 --- a/test/utils/url-generator-data2.test.coffee +++ b/test/utils/url-generator-data2.test.coffee @@ -153,3 +153,9 @@ describe 'URL Generator for data queries', -> service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected + + it 'throws an error when used against pre-SDMX 3.0 services', -> + query = DataQuery2.from({context: 'dataflow=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'SDMX 3.0 queries not allowed in v1.5.0') \ No newline at end of file From 0bbaf5e6a5403c46c6d705d55feccff3656c31ad Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 11:30:31 +0200 Subject: [PATCH 26/47] Remove unnecessary import --- test/data/data-query2.test.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index bed22e2..7cf0b42 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -1,6 +1,5 @@ should = require('chai').should() -{DataDetail} = require '../../src/data/data-detail' {DataQuery2} = require '../../src/data/data-query2' describe 'Data queries', -> From b0ae0d3d7ab9e4f18de6a19b53c634e220b845c9 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 11:32:26 +0200 Subject: [PATCH 27/47] Change test description --- test/data/data-query2.test.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 7cf0b42..78ec006 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -2,7 +2,7 @@ should = require('chai').should() {DataQuery2} = require '../../src/data/data-query2' -describe 'Data queries', -> +describe 'SDMX 3.0 data queries', -> it 'has the expected properties', -> q = DataQuery2.from {} From 7e4919c1aff64fc2e35b6411271a025038decda4 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 11:50:49 +0200 Subject: [PATCH 28/47] Add SDMX 3.0 availability query --- src/avail/availability-query2.coffee | 78 ++++++++++ test/avail/availability-query2.test.coffee | 168 +++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/avail/availability-query2.coffee create mode 100644 test/avail/availability-query2.test.coffee diff --git a/src/avail/availability-query2.coffee b/src/avail/availability-query2.coffee new file mode 100644 index 0000000..6159ef5 --- /dev/null +++ b/src/avail/availability-query2.coffee @@ -0,0 +1,78 @@ +{AvailabilityMode} = require './availability-mode' +{AvailabilityReferences} = require './availability-references' +{ContextRefType, Sdmx3SeriesKeyType, NestedNCNameIDType, FiltersType} = + require '../utils/sdmx-patterns' +{isValidEnum, isValidPattern, isValidDate, createErrorMessage} = + require '../utils/validators' + +defaults = + context: '*=*:*(*)' + key: '*' + component: '*' + filters: [] + mode: AvailabilityMode.EXACT + references: AvailabilityReferences.NONE + +isValidKey = (input, name, errors) -> + valid = true + if input.indexOf(",") > -1 + for i in input.split "," + r = isValidPattern(i, Sdmx3SeriesKeyType, name, errors) + valid = false unless r + else + r = isValidPattern(input, Sdmx3SeriesKeyType, name, errors) + valid = false unless r + valid + +isValidFilters = (input, name, errors) -> + valid = true + for filter in input + r = isValidPattern(filter, FiltersType, name, errors) + valid = false unless r + valid + +isValidComp = (input, name, errors) -> + valid = true + if input isnt '*' + r = isValidPattern(input, NestedNCNameIDType, name, errors) + valid = false unless r + valid + +ValidQuery = + context: (i, e) -> isValidPattern(i, ContextRefType, 'context', e) + key: (i, e) -> isValidKey(i, 'series key', e) + component: (i, e) -> isValidComp(i, 'component', e) + updatedAfter: (i, e) -> not i or isValidDate(i, 'updatedAfter', e) + filters: (i, e) -> isValidFilters(i, 'filters', e) + mode: (i, e) -> isValidEnum(i, AvailabilityMode, 'mode', e) + references: (i, e) -> isValidEnum(i, AvailabilityReferences, 'references', e) + +isValidQuery = (q) -> + errors = [] + isValid = false + for own k, v of q + isValid = ValidQuery[k](v, errors) + break unless isValid + {isValid: isValid, errors: errors} + +query = class AvailabilityQuery + + @from: (opts) -> + context = opts?.context ? defaults.context + key = opts?.key ? defaults.key + filters = opts?.filters ? defaults.filters + filters = [filters] if not Array.isArray filters + query = + context: context + key: key + component: opts?.component ? defaults.component + updatedAfter: opts?.updatedAfter + filters: filters + mode: opts?.mode ? defaults.mode + references: opts?.references ? defaults.references + input = isValidQuery query + throw Error createErrorMessage(input.errors, 'availability query') \ + unless input.isValid + query + +exports.AvailabilityQuery2 = query diff --git a/test/avail/availability-query2.test.coffee b/test/avail/availability-query2.test.coffee new file mode 100644 index 0000000..a3cfbd8 --- /dev/null +++ b/test/avail/availability-query2.test.coffee @@ -0,0 +1,168 @@ +should = require('chai').should() + +{AvailabilityMode} = require '../../src/avail/availability-mode' +{AvailabilityReferences} = require '../../src/avail/availability-references' +{AvailabilityQuery2} = require '../../src/avail/availability-query2' + +describe 'SDMX 3.0 availability queries', -> + + it 'has the expected properties', -> + q = AvailabilityQuery2.from {} + q.should.be.an 'object' + q.should.have.property 'context' + q.should.have.property 'key' + q.should.have.property 'component' + q.should.have.property 'updatedAfter' + q.should.have.property 'filters' + q.should.have.property 'mode' + q.should.have.property 'references' + + it 'has the expected defaults', -> + q = AvailabilityQuery2.from {} + q.should.have.property('context').that.equals '*=*:*(*)' + q.should.have.property('key').that.equals '*' + q.should.have.property('component').that.equals '*' + q.should.have.property('updatedAfter').that.is.undefined + q.should.have.property('filters').that.is.instanceOf Array + q.should.have.property('filters').that.has.lengthOf 0 + q.should.have.property('mode').that.equals 'exact' + q.should.have.property('references').that.equals 'none' + + it 'has the expected defaults, even when nothing gets passed', -> + q = AvailabilityQuery2.from null + q.should.have.property('context').that.equals '*=*:*(*)' + q.should.have.property('key').that.equals '*' + q.should.have.property('component').that.equals '*' + q.should.have.property('updatedAfter').that.is.undefined + q.should.have.property('filters').that.has.lengthOf 0 + q.should.have.property('mode').that.equals 'exact' + q.should.have.property('references').that.equals 'none' + + + describe 'when setting the context', -> + + it 'throws an exception when the context is invalid', -> + test = -> AvailabilityQuery2.from({context: '1%'}) + should.Throw(test, Error, 'Not a valid availability query') + + it 'accepts the usual context types', -> + c1 = 'datastructure=BIS:BIS_CBS(1.0)' + q1 = AvailabilityQuery2.from({context: c1}) + q1.should.have.property('context').that.equals c1 + + c2 = 'dataflow=BIS:CBS(1.0)' + q2 = AvailabilityQuery2.from({context: c2}) + q2.should.have.property('context').that.equals c2 + + c3 = 'provisionagreement=BIS:CBS_5B0(1.0)' + q3 = AvailabilityQuery2.from({context: c3}) + q3.should.have.property('context').that.equals c3 + + it 'accepts the new wildcard types', -> + c = 'dataflow=BIS:*(~)' + q = AvailabilityQuery2.from({context: c}) + q.should.have.property('context').that.equals c + + it 'accepts multiple values', -> + c = 'dataflow=BIS:BIS_CBS,BIS_LBS(*)' + q = AvailabilityQuery2.from({context: c}) + q.should.have.property('context').that.equals c + + describe 'when setting the key', -> + + it 'a string representing the key can be used', -> + key = 'M.CHF.EUR.SP00.A' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) + q.should.have.property('key').that.equals key + + it 'a string with wildcarded values can be used', -> + key = 'M.*.EUR.SP00.*' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) + q.should.have.property('key').that.equals key + + it 'a string with multiple keys can be used', -> + key = 'M.CHF.EUR.SP00.A,D.CHF.EUR.SP00.A' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: key}) + q.should.have.property('key').that.equals key + + it 'throws an exception if the value for the key is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: 'M.CHF+NOK.EUR..'}) + should.Throw(test, Error, 'Not a valid availability query') + + it 'throws an exception if one of the values for the key is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', key: 'M.CHF.EUR,M.USD+GBP.EUR'}) + should.Throw(test, Error, 'Not a valid availability query') + + describe 'when setting the component ID', -> + it 'a string representing the component id can be passed', -> + cp = 'A' + query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: cp}) + query.should.have.property('component').that.equals cp + + it 'throws an exception if the component id is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: ' '}) + should.Throw(test, Error, 'Not a valid availability query') + + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: 'A*'}) + should.Throw(test, Error, 'Not a valid availability query') + + describe 'when setting the updatedAfter timestamp', -> + + it 'a string representing a timestamp can be passed', -> + last = '2016-03-04T09:57:00Z' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: last}) + q.should.have.property('updatedAfter').that.equals last + + it 'throws an exception if the value for updatedAfter is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: 'now'}) + should.Throw(test, Error, 'Not a valid availability query') + + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', updatedAfter: '2000-Q1'}) + should.Throw(test, Error, 'Not a valid availability query') + + describe 'when setting the filters to be applied', -> + + it 'a string representing one filter can be passed', -> + f = 'FREQ=A' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: f}) + q.should.have.property('filters').that.has.lengthOf 1 + r = q.filters[0] + r.should.equal f + + it 'an array representing multiple filters can be passed', -> + f1 = 'FREQ=A' + f2 = 'TIME_PERIOD=ge:2020-01+le:2020-12,2022-08' + q = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: [f1, f2]}) + q.should.have.property('filters').that.has.lengthOf 2 + r1 = q.filters[0] + r1.should.equal f1 + r2 = q.filters[1] + r2.should.equal f2 + + it 'throws an exception if the filter is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: 'FREQ=badop:UNIT'}) + should.Throw(test, Error, 'Not a valid availability query') + + it 'throws an exception if one of the filters is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: ['FREQ=A', '$1']}) + should.Throw(test, Error, 'Not a valid availability query') + + describe 'when setting the processing mode', -> + it 'a string representing the amount of details can be passed', -> + mode = AvailabilityMode.AVAILABLE + query = AvailabilityQuery2.from({flow: 'EXR', mode: mode}) + query.should.have.property('mode').that.equals mode + + it 'throws an exception if the value for mode is unknown', -> + test = -> AvailabilityQuery2.from({flow: 'EXR', mode: 'test'}) + should.Throw(test, Error, 'Not a valid availability query') + + describe 'when setting the references', -> + it 'a string representing the references to be resolved can be passed', -> + refs = AvailabilityReferences.CONCEPT_SCHEME + query = AvailabilityQuery2.from({flow: 'EXR', references: refs}) + query.should.have.property('references').that.equals refs + + it 'throws an exception if the value for references is unknown', -> + test = -> AvailabilityQuery2.from({flow: 'dataflow', references: 'ref'}) + should.Throw(test, Error, 'Not a valid availability query') \ No newline at end of file From 320792c6e109c22e9bcb38d08a1e5e59555e8cdc Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 11:56:23 +0200 Subject: [PATCH 29/47] Check that multiple components can be passed --- test/avail/availability-query2.test.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/avail/availability-query2.test.coffee b/test/avail/availability-query2.test.coffee index a3cfbd8..0de50ba 100644 --- a/test/avail/availability-query2.test.coffee +++ b/test/avail/availability-query2.test.coffee @@ -99,6 +99,11 @@ describe 'SDMX 3.0 availability queries', -> query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: cp}) query.should.have.property('component').that.equals cp + it 'a string representing multilpe component ids can be passed', -> + cp = 'A' + query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: cp}) + query.should.have.property('component').that.equals cp + it 'throws an exception if the component id is invalid', -> test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: ' '}) should.Throw(test, Error, 'Not a valid availability query') From 32ca5d24d5c3350c63727a4f6357dfc8fdcca26b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:01:29 +0200 Subject: [PATCH 30/47] Check that multiple components can really be used --- src/avail/availability-query2.coffee | 9 +++++++-- test/avail/availability-query2.test.coffee | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/avail/availability-query2.coffee b/src/avail/availability-query2.coffee index 6159ef5..d3ca088 100644 --- a/src/avail/availability-query2.coffee +++ b/src/avail/availability-query2.coffee @@ -34,8 +34,13 @@ isValidFilters = (input, name, errors) -> isValidComp = (input, name, errors) -> valid = true if input isnt '*' - r = isValidPattern(input, NestedNCNameIDType, name, errors) - valid = false unless r + if input.indexOf(",") > -1 + for i in input.split "," + r = isValidPattern(i, NestedNCNameIDType, name, errors) + valid = false unless r + else + r = isValidPattern(input, NestedNCNameIDType, name, errors) + valid = false unless r valid ValidQuery = diff --git a/test/avail/availability-query2.test.coffee b/test/avail/availability-query2.test.coffee index 0de50ba..45c7f82 100644 --- a/test/avail/availability-query2.test.coffee +++ b/test/avail/availability-query2.test.coffee @@ -100,7 +100,7 @@ describe 'SDMX 3.0 availability queries', -> query.should.have.property('component').that.equals cp it 'a string representing multilpe component ids can be passed', -> - cp = 'A' + cp = 'A,B' query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: cp}) query.should.have.property('component').that.equals cp From 1c5d858cbd29019c7b0394c5fe1563ebad1cbe89 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:05:41 +0200 Subject: [PATCH 31/47] Update test description --- test/utils/url-generator-data2.test.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/url-generator-data2.test.coffee b/test/utils/url-generator-data2.test.coffee index be705ca..dd8bf21 100644 --- a/test/utils/url-generator-data2.test.coffee +++ b/test/utils/url-generator-data2.test.coffee @@ -5,7 +5,7 @@ should = require('chai').should() {DataQuery2} = require '../../src/data/data-query2' {UrlGenerator} = require '../../src/utils/url-generator' -describe 'URL Generator for data queries', -> +describe 'URL Generator for SDMX 3.0 data queries', -> it 'generates a URL for a full data query', -> expected = "http://test.com/data/dataflow/*/EXR/*/A..EUR.SP00.A\ From d488e9127ab19c895168bde4db7ffff1d87c325e Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:16:59 +0200 Subject: [PATCH 32/47] Improve branching --- src/utils/url-generator-data2.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 6561228..6431891 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -68,7 +68,7 @@ handler = class Handler api = ApiNumber[getKeyFromVersion(s.api)] if api < ApiNumber.v2_0_0 throw Error "SDMX 3.0 queries not allowed in #{s.api}" - if skip + else if skip createShortDataQuery(q, s, api) else createDataQuery(q, s, api) From 365e10dde8152f543ba6e711339462f85af96aca Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:17:52 +0200 Subject: [PATCH 33/47] Start implementation of new availability handler --- src/utils/url-generator-availability2.coffee | 100 +++++++++++++++++++ src/utils/url-generator.coffee | 3 + 2 files changed, 103 insertions(+) create mode 100644 src/utils/url-generator-availability2.coffee diff --git a/src/utils/url-generator-availability2.coffee b/src/utils/url-generator-availability2.coffee new file mode 100644 index 0000000..d18ac45 --- /dev/null +++ b/src/utils/url-generator-availability2.coffee @@ -0,0 +1,100 @@ +{ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' +{createEntryPoint, validateDataForV2, parseFlow} = + require '../utils/url-generator-common' + +handleAvailabilityPathParams = (q) -> + path = [] + path.push q.component unless q.component is 'all' + path.push q.provider if q.provider isnt 'all' or path.length + path.push q.key if q.key isnt 'all' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +handleAvailabilityQueryParams = (q) -> + p = [] + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "endPeriod=#{q.end}" if q.end + p.push "startPeriod=#{q.start}" if q.start + p.push "mode=#{q.mode}" unless q.mode is 'exact' + p.push "references=#{q.references}" unless q.references is 'none' + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +handleAvailabilityV2PathParams = (q) -> + path = [] + path.push q.component unless q.component is 'all' + k = if q.key is 'all' then '*' else q.key + path.push k if k isnt '*' or path.length + if path.length then '/' + path.reverse().join('/') else '' + +handleAvailabilityV2QueryParams = (q) -> + p = [] + p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter + p.push "mode=#{q.mode}" unless q.mode is 'exact' + p.push "references=#{q.references}" unless q.references is 'none' + if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' + +createShortV1AvailUrl = (q, s) -> + u = createEntryPoint s + u += "availableconstraint/#{q.flow}" + u += handleAvailabilityPathParams(q) + u += handleAvailabilityQueryParams(q) + u + +createShortV2AvailUrl = (q, s) -> + validateDataForV2 q, s + u = createEntryPoint s + u += "availableconstraint/dataflow/" + fc = parseFlow q.flow + u += "#{fc[0]}/#{fc[1]}" + pp = handleAvailabilityV2PathParams(q) + if fc[2] isnt "*" or pp isnt "" + u += "/#{fc[2]}" + u += pp + u += handleAvailabilityV2QueryParams(q) + u + +createV1AvailUrl = (q, s) -> + url = createEntryPoint s + url += 'availableconstraint' + url += "/#{q.flow}/#{q.key}/#{q.provider}/#{q.component}" + url += "?mode=#{q.mode}&references=#{q.references}" + url += "&startPeriod=#{q.start}" if q.start + url += "&endPeriod=#{q.end}" if q.end + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url + +createV2AvailUrl = (q, s) -> + validateDataForV2 q, s + url = createEntryPoint s + url += 'availableconstraint' + fc = parseFlow q.flow + url += "/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" + url += if q.key == "all" then "*/" else "#{q.key}/" + url += if q.component == "all" then "*" else "#{q.component}" + url += "?mode=#{q.mode}&references=#{q.references}" + url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter + url + +createShortAvailabilityQuery = (q, s, api_number) -> + if api_number < ApiNumber.v2_0_0 + createShortV1AvailUrl q, s + else + createShortV2AvailUrl q, s + +createAvailabilityQuery = (q, s, api_number) -> + if api_number < ApiNumber.v2_0_0 + createV1AvailUrl q, s + else + createV2AvailUrl q, s + +handler = class Handler + + handle: (q, s, skip) -> + api = ApiNumber[getKeyFromVersion(s.api)] + if api < ApiNumber.v2_0_0 + throw Error "SDMX 3.0 queries not allowed in #{s.api}" + else if skip + createShortAvailabilityQuery(q, s, api) + else + createAvailabilityQuery(q, s, api) + +exports.AvailabilityQuery2Handler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 3444a30..24addbb 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -1,5 +1,6 @@ {ApiVersion} = require '../utils/api-version' {AvailabilityQueryHandler} = require '../utils/url-generator-availability' +{AvailabilityQuery2Handler} = require '../utils/url-generator-availability2' {SchemaQueryHandler} = require '../utils/url-generator-schema' {DataQueryHandler} = require '../utils/url-generator-data' {Data2QueryHandler} = require '../utils/url-generator-data2' @@ -13,6 +14,8 @@ generator = class Generator unless @service and @service.url if @query.context? and @query.attributes? new Data2QueryHandler().handle(@query, @service, skipDefaults) + else if @query.context? and @query.mode? + new AvailabilityQuery2Handler().handle(@query, @service, skipDefaults) else if @query.mode? new AvailabilityQueryHandler().handle(@query, @service, skipDefaults) else if @query.flow? From c24731e3edc2b1ff31f5d79cbebfebdab30eb574 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:19:05 +0200 Subject: [PATCH 34/47] Align naming conventions --- src/utils/url-generator-data2.coffee | 2 +- src/utils/url-generator.coffee | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 6431891..d8a06ec 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -73,4 +73,4 @@ handler = class Handler else createDataQuery(q, s, api) -exports.Data2QueryHandler = handler +exports.DataQuery2Handler = handler diff --git a/src/utils/url-generator.coffee b/src/utils/url-generator.coffee index 24addbb..dd0b834 100644 --- a/src/utils/url-generator.coffee +++ b/src/utils/url-generator.coffee @@ -3,7 +3,7 @@ {AvailabilityQuery2Handler} = require '../utils/url-generator-availability2' {SchemaQueryHandler} = require '../utils/url-generator-schema' {DataQueryHandler} = require '../utils/url-generator-data' -{Data2QueryHandler} = require '../utils/url-generator-data2' +{DataQuery2Handler} = require '../utils/url-generator-data2' {MetadataQueryHandler} = require '../utils/url-generator-metadata' generator = class Generator @@ -13,7 +13,7 @@ generator = class Generator throw ReferenceError "#{@service} is not a valid service"\ unless @service and @service.url if @query.context? and @query.attributes? - new Data2QueryHandler().handle(@query, @service, skipDefaults) + new DataQuery2Handler().handle(@query, @service, skipDefaults) else if @query.context? and @query.mode? new AvailabilityQuery2Handler().handle(@query, @service, skipDefaults) else if @query.mode? From 21091e6997c66810ed2c514f263a752bfa0cf29c Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 12:26:37 +0200 Subject: [PATCH 35/47] Move parseFilter to common file --- src/utils/url-generator-common.coffee | 8 +++++++- src/utils/url-generator-data2.coffee | 8 +------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/url-generator-common.coffee b/src/utils/url-generator-common.coffee index d626828..568c602 100644 --- a/src/utils/url-generator-common.coffee +++ b/src/utils/url-generator-common.coffee @@ -21,6 +21,11 @@ contextPattern = /// parseContext = (f) -> f.match(contextPattern)[1..4] +filterPattern = /// + (.*)=(.*) +/// + +parseFilter = (f) -> f.match(filterPattern)[1..2] validateDataForV2 = (q, s) -> if q.provider? and q.provider isnt "all" @@ -47,4 +52,5 @@ exports.parseFlow = parseFlow exports.validateDataForV2 = validateDataForV2 exports.checkVersion = checkVersion exports.checkMultipleItems = checkMultipleItems -exports.parseContext = parseContext \ No newline at end of file +exports.parseContext = parseContext +exports.parseFilter = parseFilter \ No newline at end of file diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index d8a06ec..0f694df 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -1,13 +1,7 @@ {ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' -{createEntryPoint, validateDataForV2, parseContext} = +{createEntryPoint, validateDataForV2, parseContext, parseFilter} = require '../utils/url-generator-common' -filterPattern = /// - (.*)=(.*) -/// - -parseFilter = (f) -> f.match(filterPattern)[1..2] - createDataQuery = (q, s, a) -> validateDataForV2 q, s url = createEntryPoint s From c48c2438df4ceab4f1f1d86e8323675c94d7b28d Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 13:37:25 +0200 Subject: [PATCH 36/47] Remove unnecessary params --- src/utils/url-generator-data2.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/url-generator-data2.coffee b/src/utils/url-generator-data2.coffee index 0f694df..4fc4d64 100644 --- a/src/utils/url-generator-data2.coffee +++ b/src/utils/url-generator-data2.coffee @@ -2,7 +2,7 @@ {createEntryPoint, validateDataForV2, parseContext, parseFilter} = require '../utils/url-generator-common' -createDataQuery = (q, s, a) -> +createDataQuery = (q, s) -> validateDataForV2 q, s url = createEntryPoint s fc = parseContext q.context @@ -32,7 +32,7 @@ handleDataPathParams = (q) -> p.push c[0] if c[0] isnt '*' or p.length if p.length then '/' + p.reverse().join('/') else '' -handleDataQueryParams = (q, s, a) -> +handleDataQueryParams = (q) -> p = [] if q.filters for filter in q.filters @@ -47,13 +47,13 @@ handleDataQueryParams = (q, s, a) -> p.push "lastNObservations=#{q.lastNObs}" if q.lastNObs if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' -createShortDataQuery = (q, s, a) -> +createShortDataQuery = (q, s) -> validateDataForV2 q, s u = createEntryPoint s u += 'data' p = handleDataPathParams(q) u += p - u += handleDataQueryParams(q, s, a) + u += handleDataQueryParams(q) u handler = class Handler @@ -63,8 +63,8 @@ handler = class Handler if api < ApiNumber.v2_0_0 throw Error "SDMX 3.0 queries not allowed in #{s.api}" else if skip - createShortDataQuery(q, s, api) + createShortDataQuery(q, s) else - createDataQuery(q, s, api) + createDataQuery(q, s) exports.DataQuery2Handler = handler From 82ce304edcfea362a26de351d6f61d387395a53d Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 13:38:23 +0200 Subject: [PATCH 37/47] Implement url generation for new availability query --- src/utils/url-generator-availability2.coffee | 98 ++++---------- .../url-generator-availability2.test.coffee | 125 ++++++++++++++++++ 2 files changed, 153 insertions(+), 70 deletions(-) create mode 100644 test/utils/url-generator-availability2.test.coffee diff --git a/src/utils/url-generator-availability2.coffee b/src/utils/url-generator-availability2.coffee index d18ac45..824f869 100644 --- a/src/utils/url-generator-availability2.coffee +++ b/src/utils/url-generator-availability2.coffee @@ -1,91 +1,49 @@ {ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' -{createEntryPoint, validateDataForV2, parseFlow} = +{createEntryPoint, validateDataForV2, parseContext} = require '../utils/url-generator-common' -handleAvailabilityPathParams = (q) -> - path = [] - path.push q.component unless q.component is 'all' - path.push q.provider if q.provider isnt 'all' or path.length - path.push q.key if q.key isnt 'all' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -handleAvailabilityQueryParams = (q) -> +handlePathParams = (q) -> p = [] - p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter - p.push "endPeriod=#{q.end}" if q.end - p.push "startPeriod=#{q.start}" if q.start - p.push "mode=#{q.mode}" unless q.mode is 'exact' - p.push "references=#{q.references}" unless q.references is 'none' - if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -handleAvailabilityV2PathParams = (q) -> - path = [] - path.push q.component unless q.component is 'all' - k = if q.key is 'all' then '*' else q.key - path.push k if k isnt '*' or path.length - if path.length then '/' + path.reverse().join('/') else '' - -handleAvailabilityV2QueryParams = (q) -> + c = parseContext q.context + p.push q.component unless q.component is '*' + p.push q.key if q.key isnt '*' or p.length + p.push c[3] if c[3] isnt '*' or p.length + p.push c[2] if c[2] isnt '*' or p.length + p.push c[1] if c[1] isnt '*' or p.length + p.push c[0] if c[0] isnt '*' or p.length + if p.length then '/' + p.reverse().join('/') else '' + +handleQueryParams = (q) -> p = [] p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter p.push "mode=#{q.mode}" unless q.mode is 'exact' p.push "references=#{q.references}" unless q.references is 'none' if p.length > 0 then '?' + p.reduceRight (x, y) -> x + '&' + y else '' - -createShortV1AvailUrl = (q, s) -> - u = createEntryPoint s - u += "availableconstraint/#{q.flow}" - u += handleAvailabilityPathParams(q) - u += handleAvailabilityQueryParams(q) - u - -createShortV2AvailUrl = (q, s) -> + +createShortAvailabilityQuery = (q, s, api_number) -> validateDataForV2 q, s u = createEntryPoint s - u += "availableconstraint/dataflow/" - fc = parseFlow q.flow - u += "#{fc[0]}/#{fc[1]}" - pp = handleAvailabilityV2PathParams(q) - if fc[2] isnt "*" or pp isnt "" - u += "/#{fc[2]}" - u += pp - u += handleAvailabilityV2QueryParams(q) + u += "availableconstraint" + p = handlePathParams(q) + u += p + u += handleQueryParams(q) u -createV1AvailUrl = (q, s) -> - url = createEntryPoint s - url += 'availableconstraint' - url += "/#{q.flow}/#{q.key}/#{q.provider}/#{q.component}" - url += "?mode=#{q.mode}&references=#{q.references}" - url += "&startPeriod=#{q.start}" if q.start - url += "&endPeriod=#{q.end}" if q.end - url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter - url - -createV2AvailUrl = (q, s) -> +createAvailabilityQuery = (q, s, api_number) -> validateDataForV2 q, s url = createEntryPoint s - url += 'availableconstraint' - fc = parseFlow q.flow - url += "/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" - url += if q.key == "all" then "*/" else "#{q.key}/" - url += if q.component == "all" then "*" else "#{q.component}" - url += "?mode=#{q.mode}&references=#{q.references}" + fc = parseContext q.context + url += "availableconstraint/#{fc[0]}/#{fc[1]}/#{fc[2]}/#{fc[3]}/" + url += "#{q.key}/" + url += "#{q.component}?" + if q.filters + for filter in q.filters + f = parseFilter filter + url += "c[#{f[0]}]=#{f[1]}&" + url += "mode=#{q.mode}&references=#{q.references}" url += "&updatedAfter=#{q.updatedAfter}" if q.updatedAfter url -createShortAvailabilityQuery = (q, s, api_number) -> - if api_number < ApiNumber.v2_0_0 - createShortV1AvailUrl q, s - else - createShortV2AvailUrl q, s - -createAvailabilityQuery = (q, s, api_number) -> - if api_number < ApiNumber.v2_0_0 - createV1AvailUrl q, s - else - createV2AvailUrl q, s - handler = class Handler handle: (q, s, skip) -> diff --git a/test/utils/url-generator-availability2.test.coffee b/test/utils/url-generator-availability2.test.coffee new file mode 100644 index 0000000..4c9268f --- /dev/null +++ b/test/utils/url-generator-availability2.test.coffee @@ -0,0 +1,125 @@ +should = require('chai').should() + +{Service} = require '../../src/service/service' +{ApiVersion} = require '../../src/utils/api-version' +{AvailabilityQuery2} = require '../../src/avail/availability-query2' +{UrlGenerator} = require '../../src/utils/url-generator' + +describe 'URL Generator for SDMX 3.0 availability queries', -> + + it 'generates a URL for a full availability query', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ + mode=available&references=none\ + &updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery2.from({ + context: 'dataflow=*:EXR(*)' + key: 'A..EUR.SP00.A' + component: 'FREQ' + updatedAfter: '2016-03-01T00:00:00Z' + mode: 'available' + references: 'none' + }) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'generates a URL for a partial availability query', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/*?\ + mode=exact&references=none' + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + + it 'offers to skip all default values', -> + expected = 'http://test.com/availableconstraint' + query = AvailabilityQuery2.from null + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (context)', -> + expected = "http://test.com/availableconstraint/dataflow" + query = AvailabilityQuery2.from({context: 'dataflow=*:*(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (agency)', -> + expected = "http://test.com/availableconstraint/*/BIS" + query = AvailabilityQuery2.from({context: '*=BIS:*(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (artefact)', -> + expected = "http://test.com/availableconstraint/*/*/EXR" + query = AvailabilityQuery2.from({context: '*=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (version)', -> + expected = "http://test.com/availableconstraint/*/*/*/~" + query = AvailabilityQuery2.from({context: '*=*:*(~)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (key)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A.CH' + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', key: 'A.CH'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (component)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/FREQ' + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', component: 'FREQ'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (mode)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + ?mode=available' + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', mode: 'available'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (refs)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + ?references=codelist' + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', references: 'codelist'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (upd)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery2.from({ + context: 'dataflow=*:EXR(*)' + updatedAfter: '2016-03-01T00:00:00Z'}) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'offers to skip default values (multi)', -> + expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + mode=available&updatedAfter=2016-03-01T00:00:00Z' + query = AvailabilityQuery2.from({ + context: 'dataflow=*:EXR(*)' + updatedAfter: '2016-03-01T00:00:00Z', + mode: 'available' + }) + service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + + it 'throws an error when used against pre-SDMX 3.0 services', -> + query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v1_5_0}) + test = -> new UrlGenerator().getUrl(query, service) + should.Throw(test, Error, 'SDMX 3.0 queries not allowed in v1.5.0') From 202b382a018e31c7d0230f76904bdfad25f64042 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 13:48:18 +0200 Subject: [PATCH 38/47] Rename to availability and add filters --- src/utils/url-generator-availability2.coffee | 10 ++-- .../url-generator-availability2.test.coffee | 48 ++++++++++++------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/utils/url-generator-availability2.coffee b/src/utils/url-generator-availability2.coffee index 824f869..88744e1 100644 --- a/src/utils/url-generator-availability2.coffee +++ b/src/utils/url-generator-availability2.coffee @@ -1,5 +1,5 @@ {ApiNumber, ApiVersion, getKeyFromVersion} = require '../utils/api-version' -{createEntryPoint, validateDataForV2, parseContext} = +{createEntryPoint, validateDataForV2, parseContext, parseFilter} = require '../utils/url-generator-common' handlePathParams = (q) -> @@ -15,6 +15,10 @@ handlePathParams = (q) -> handleQueryParams = (q) -> p = [] + if q.filters + for filter in q.filters + f = parseFilter filter + p.push "c[#{f[0]}]=#{f[1]}" p.push "updatedAfter=#{q.updatedAfter}" if q.updatedAfter p.push "mode=#{q.mode}" unless q.mode is 'exact' p.push "references=#{q.references}" unless q.references is 'none' @@ -23,7 +27,7 @@ handleQueryParams = (q) -> createShortAvailabilityQuery = (q, s, api_number) -> validateDataForV2 q, s u = createEntryPoint s - u += "availableconstraint" + u += "availability" p = handlePathParams(q) u += p u += handleQueryParams(q) @@ -33,7 +37,7 @@ createAvailabilityQuery = (q, s, api_number) -> validateDataForV2 q, s url = createEntryPoint s fc = parseContext q.context - url += "availableconstraint/#{fc[0]}/#{fc[1]}/#{fc[2]}/#{fc[3]}/" + url += "availability/#{fc[0]}/#{fc[1]}/#{fc[2]}/#{fc[3]}/" url += "#{q.key}/" url += "#{q.component}?" if q.filters diff --git a/test/utils/url-generator-availability2.test.coffee b/test/utils/url-generator-availability2.test.coffee index 4c9268f..cbe8302 100644 --- a/test/utils/url-generator-availability2.test.coffee +++ b/test/utils/url-generator-availability2.test.coffee @@ -8,8 +8,8 @@ should = require('chai').should() describe 'URL Generator for SDMX 3.0 availability queries', -> it 'generates a URL for a full availability query', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ - mode=available&references=none\ + expected = 'http://test.com/availability/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ + c[FREQ]=A&mode=available&references=none\ &updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery2.from({ context: 'dataflow=*:EXR(*)' @@ -18,86 +18,95 @@ describe 'URL Generator for SDMX 3.0 availability queries', -> updatedAfter: '2016-03-01T00:00:00Z' mode: 'available' references: 'none' + filters: 'FREQ=A' }) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service) url.should.equal expected it 'generates a URL for a partial availability query', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/*?\ + expected = 'http://test.com/availability/dataflow/*/EXR/*/*/*?\ mode=exact&references=none' query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service) url.should.equal expected + it 'generates a URL with all default values', -> + expected = 'http://test.com/availability/*/*/*/*/*/*?\ + mode=exact&references=none' + query = AvailabilityQuery2.from null + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service) + url.should.equal expected + it 'offers to skip all default values', -> - expected = 'http://test.com/availableconstraint' + expected = 'http://test.com/availability' query = AvailabilityQuery2.from null service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (context)', -> - expected = "http://test.com/availableconstraint/dataflow" + expected = "http://test.com/availability/dataflow" query = AvailabilityQuery2.from({context: 'dataflow=*:*(*)'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (agency)', -> - expected = "http://test.com/availableconstraint/*/BIS" + expected = "http://test.com/availability/*/BIS" query = AvailabilityQuery2.from({context: '*=BIS:*(*)'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (artefact)', -> - expected = "http://test.com/availableconstraint/*/*/EXR" + expected = "http://test.com/availability/*/*/EXR" query = AvailabilityQuery2.from({context: '*=*:EXR(*)'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (version)', -> - expected = "http://test.com/availableconstraint/*/*/*/~" + expected = "http://test.com/availability/*/*/*/~" query = AvailabilityQuery2.from({context: '*=*:*(~)'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (key)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A.CH' + expected = 'http://test.com/availability/dataflow/*/EXR/*/A.CH' query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', key: 'A.CH'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (component)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/FREQ' + expected = 'http://test.com/availability/dataflow/*/EXR/*/*/FREQ' query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', component: 'FREQ'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected it 'offers to skip default values (mode)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + expected = 'http://test.com/availability/dataflow/*/EXR\ ?mode=available' query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', mode: 'available'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected - it 'offers to skip default values (refs)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + it 'offers to skip default values (referencess)', -> + expected = 'http://test.com/availability/dataflow/*/EXR\ ?references=codelist' query = AvailabilityQuery2.from({context: 'dataflow=*:EXR(*)', references: 'codelist'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected - it 'offers to skip default values (upd)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + it 'offers to skip default values (updatedAfter)', -> + expected = 'http://test.com/availability/dataflow/*/EXR?\ updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery2.from({ context: 'dataflow=*:EXR(*)' @@ -106,8 +115,15 @@ describe 'URL Generator for SDMX 3.0 availability queries', -> url = new UrlGenerator().getUrl(query, service, true) url.should.equal expected + it 'offers to skip default values (filters)', -> + expected = "http://test.com/availability?c[REF_AREA]=UY,AR" + query = AvailabilityQuery2.from({filters: 'REF_AREA=UY,AR'}) + service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) + url = new UrlGenerator().getUrl(query, service, true) + url.should.equal expected + it 'offers to skip default values (multi)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + expected = 'http://test.com/availability/dataflow/*/EXR?\ mode=available&updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery2.from({ context: 'dataflow=*:EXR(*)' From 741f9b658757a05f60bbc51884ee333026383eac Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 13:50:51 +0200 Subject: [PATCH 39/47] Improve coverage --- test/avail/availability-query2.test.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/avail/availability-query2.test.coffee b/test/avail/availability-query2.test.coffee index 45c7f82..9aa36ee 100644 --- a/test/avail/availability-query2.test.coffee +++ b/test/avail/availability-query2.test.coffee @@ -111,6 +111,10 @@ describe 'SDMX 3.0 availability queries', -> test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: 'A*'}) should.Throw(test, Error, 'Not a valid availability query') + it 'throws an exception one of the component ids is invalid', -> + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', component: 'A,Q*'}) + should.Throw(test, Error, 'Not a valid availability query') + describe 'when setting the updatedAfter timestamp', -> it 'a string representing a timestamp can be passed', -> From 8c306bf9f1f919cbfb54801be0e35535f1a4cfe7 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 13:55:58 +0200 Subject: [PATCH 40/47] Resource renamed to availability in SDMX 3.0 --- src/utils/url-generator-availability.coffee | 4 ++-- test/index.test.coffee | 8 ++++---- .../url-generator-availability.test.coffee | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/utils/url-generator-availability.coffee b/src/utils/url-generator-availability.coffee index f458aa4..4d42716 100644 --- a/src/utils/url-generator-availability.coffee +++ b/src/utils/url-generator-availability.coffee @@ -42,7 +42,7 @@ createShortV1AvailUrl = (q, s) -> createShortV2AvailUrl = (q, s) -> validateDataForV2 q, s u = createEntryPoint s - u += "availableconstraint/dataflow/" + u += "availability/dataflow/" fc = parseFlow q.flow u += "#{fc[0]}/#{fc[1]}" pp = handleAvailabilityV2PathParams(q) @@ -65,7 +65,7 @@ createV1AvailUrl = (q, s) -> createV2AvailUrl = (q, s) -> validateDataForV2 q, s url = createEntryPoint s - url += 'availableconstraint' + url += 'availability' fc = parseFlow q.flow url += "/dataflow/#{fc[0]}/#{fc[1]}/#{fc[2]}/" url += if q.key == "all" then "*/" else "#{q.key}/" diff --git a/test/index.test.coffee b/test/index.test.coffee index 8c7036c..ec7cf7f 100644 --- a/test/index.test.coffee +++ b/test/index.test.coffee @@ -230,7 +230,7 @@ describe 'API', -> url = sdmxrest.getUrl q, s url.should.be.a 'string' url.should.contain 'http://ws-entry-point' - url.should.contain 'availableconstraint' + url.should.contain 'availability' url.should.contain 'EXR' url.should.contain 'A..EUR.SP00.A' @@ -244,7 +244,7 @@ describe 'API', -> url = sdmxrest.getUrl q, s url.should.be.a 'string' url.should.contain 'http://ws-entry-point' - url.should.contain 'availableconstraint' + url.should.contain 'availability' url.should.contain 'EXR' url.should.contain 'A..EUR.SP00.A' url.should.contain 'mode=exact' @@ -259,7 +259,7 @@ describe 'API', -> url = sdmxrest.getUrl q, s url.should.be.a 'string' url.should.contain 'http://ws-entry-point' - url.should.contain 'availableconstraint' + url.should.contain 'availability' url.should.contain 'EXR' url.should.contain 'A..EUR.SP00.A' url.should.contain 'FREQ' @@ -274,7 +274,7 @@ describe 'API', -> url = sdmxrest.getUrl q, s url.should.be.a 'string' url.should.contain 'http://ws-entry-point' - url.should.contain 'availableconstraint' + url.should.contain 'availability' url.should.contain 'EXR' url.should.contain 'A..EUR.SP00.A' url.should.contain 'references=all' diff --git a/test/utils/url-generator-availability.test.coffee b/test/utils/url-generator-availability.test.coffee index 59093b3..4d45043 100644 --- a/test/utils/url-generator-availability.test.coffee +++ b/test/utils/url-generator-availability.test.coffee @@ -27,7 +27,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'generates a URL for a full availability query (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ + expected = 'http://test.com/availability/dataflow/*/EXR/*/A..EUR.SP00.A/FREQ?\ mode=available&references=none\ &updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery.from({ @@ -51,7 +51,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'generates a URL for a partial availability query (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/*?\ + expected = 'http://test.com/availability/dataflow/*/EXR/*/*/*?\ mode=exact&references=none' query = AvailabilityQuery.from({flow: 'EXR'}) service = Service.from({url: 'http://test.com', api: ApiVersion.v2_0_0}) @@ -84,7 +84,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip default values for availability (2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR' + expected = 'http://test.com/availability/dataflow/*/EXR' query = AvailabilityQuery.from({ flow: 'EXR' mode: 'exact' @@ -95,7 +95,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (key, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/A.CH' + expected = 'http://test.com/availability/dataflow/*/EXR/*/A.CH' query = AvailabilityQuery.from({flow: 'EXR', key: 'A.CH'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) @@ -116,7 +116,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (component, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR/*/*/FREQ' + expected = 'http://test.com/availability/dataflow/*/EXR/*/*/FREQ' query = AvailabilityQuery.from({flow: 'EXR', component: 'FREQ'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) url = new UrlGenerator().getUrl(query, service, true) @@ -130,7 +130,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (mode, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + expected = 'http://test.com/availability/dataflow/*/EXR\ ?mode=available' query = AvailabilityQuery.from({flow: 'EXR', mode: 'available'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) @@ -145,7 +145,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (refs, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR\ + expected = 'http://test.com/availability/dataflow/*/EXR\ ?references=codelist' query = AvailabilityQuery.from({flow: 'EXR', references: 'codelist'}) service = Service.from({url: 'http://test.com', api: 'v2.0.0'}) @@ -185,7 +185,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (upd, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + expected = 'http://test.com/availability/dataflow/*/EXR?\ updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery.from({ flow: 'EXR' @@ -195,7 +195,7 @@ describe 'URL Generator for availability queries', -> url.should.equal expected it 'offers to skip defaults but adds them when needed (multi, 2.0.0)', -> - expected = 'http://test.com/availableconstraint/dataflow/*/EXR?\ + expected = 'http://test.com/availability/dataflow/*/EXR?\ mode=available&updatedAfter=2016-03-01T00:00:00Z' query = AvailabilityQuery.from({ flow: 'EXR' From f862f70127a0ee9d2f7ce4f1bdc73c24b65fbf7b Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 14:13:47 +0200 Subject: [PATCH 41/47] Add new getDataQuery2 --- src/index.coffee | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/index.coffee b/src/index.coffee index 6483452..0cd21bc 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -1,4 +1,5 @@ {DataQuery} = require './data/data-query' +{DataQuery2} = require './data/data-query2' {DataFormat} = require './data/data-format' {DataDetail} = require './data/data-detail' {MetadataQuery} = require './metadata/metadata-query' @@ -175,6 +176,54 @@ getService = (input) -> getDataQuery = (input) -> return DataQuery.from input +# +# Get an SDMX 3.0 RESTful data query. +# +# The expected properties (and their default values) are: +# - *context* (optional) - the reference to the context (default: *=*:*(*)). +# - *key* (optional) - the key of the data to be returned (default: all). +# - *updatedAfter* (optional) - instructs the service to return what has +# changed since the supplied time stamp. +# - *firstNObs* (optional) - the number of observations to be returned, +# starting from the first observation. +# - *lastNObs* (optional) - the number of observations to be returned, +# starting from the last observation. +# - *obsDimension* (optional) - the ID of the dimension to be attached at the +# observation level (default TIME_PERIOD). +# - *history* (optional) - Whether previous versions of the data should be +# returned (default: false). +# - *attributes* (optional) - The attributes to be returned (default: dsd). +# - *measures* (optional) - The measures to be returned (default: all). +# - *filters* (optional) - The component filters to be applied. +# +# @example Create a query for all data belonging to the CBS dataflow, +# maintained by the BIS +# sdmxrest.getDataQuery({context: 'dataflow=BIS:EXR(*)'}) +# +# @example Create a query for EXR data, matching values A for the 1st +# dimension, any value for the 2nd dimension, EUR, SP00 and A for the 3rd, 4th +# and 5th dimensions respectively +# sdmxrest.getDataQuery({context: 'dataflow=*:EXR(*)', key: 'A..EUR.SP00.A'}) +# +# @example Create a query for the last observation of the EXR data matching the +# supplied key +# sdmxrest.getDataQuery({context: 'dataflow=*:EXR(*)', key: 'A.*.EUR.SP00.A', +# lastNObs: 1}) +# +# @example Create a query to get what has changed for the EXR data since +# the supplied point in time +# sdmxrest.getDataQuery({context: 'dataflow=*:EXR(*)', +# updatedAfter: '2016-03-17T14:38:00Z'}) +# +# @param [Object] input an object with the desired filters for the query +# +# @throw an error in case a) the mandatory flow is not supplied or b) a value +# not compliant with the SDMX 2.1 RESTful specification is supplied for one of +# the properties. +# +getDataQuery2 = (input) -> + return DataQuery2.from input + # # Get an SDMX 2.1 RESTful metadata query. # @@ -380,6 +429,7 @@ module.exports = getService: getService services: services getDataQuery: getDataQuery + getDataQuery2: getDataQuery2 getMetadataQuery: getMetadataQuery getAvailabilityQuery: getAvailabilityQuery getSchemaQuery: getSchemaQuery From ecd2f261fc3fe49fbc3514ed7f5f79b84d249592 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 14:23:09 +0200 Subject: [PATCH 42/47] Add new getAvailabillityQuery2 --- src/index.coffee | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/index.coffee b/src/index.coffee index 0cd21bc..02cbd70 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -8,6 +8,7 @@ {MetadataReferences} = require './metadata/metadata-references' {MetadataType} = require './metadata/metadata-type' {AvailabilityQuery} = require './avail/availability-query' +{AvailabilityQuery2} = require './avail/availability-query2' {AvailabilityMode} = require './avail/availability-mode' {AvailabilityReferences} = require './avail/availability-references' {SchemaQuery} = require './schema/schema-query' @@ -286,6 +287,32 @@ getMetadataQuery = (input) -> getAvailabilityQuery = (input) -> return AvailabilityQuery.from input +# +# Get an SDMX 3.0 RESTful availability query. +# +# The expected properties (and their default values) are: +# - *context* (optional) - the reference to the context (default: *=*:*(*)). +# - *key* (optional) - the key of the data to be returned (default: all) +# - *component* (optional) - the id of the dimension for which to obtain +# availability information (default: all) +# - *updatedAfter* (optional) - instructs the service to return what has +# changed since the supplied time stamp. +# - *mode* (optional) - the possible processing modes (default: exact) +# - *references* (optional) - the references to be returned (default: none) +# - *filters* (optional) - The component filters to be applied. +# +# @example Create an availability query for the ECB EXR dataflow +# sdmxrest.getAvailabilityQuery({context: 'dataflow=ECB:EXR(*)'}) +# +# @param [Object] input an object with the desired characteristics of the query +# +# @throw an error in case a) the mandatory flow is not supplied or b) a value +# not compliant with the SDMX 2.1 RESTful specification is supplied for one of +# the properties. +# +getAvailabilityQuery2 = (input) -> + return AvailabilityQuery2.from input + # # Get an SDMX 2.1 RESTful schema query. # @@ -432,6 +459,7 @@ module.exports = getDataQuery2: getDataQuery2 getMetadataQuery: getMetadataQuery getAvailabilityQuery: getAvailabilityQuery + getAvailabilityQuery2: getAvailabilityQuery2 getSchemaQuery: getSchemaQuery getUrl: getUrl request: request From ff6e6d736634d45a3ae2f22747fe425ed2359a13 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 14:51:31 +0200 Subject: [PATCH 43/47] Check that the input has the expected properties --- src/data/data-query2.coffee | 17 +++++++++++++++++ test/data/data-query2.test.coffee | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/data/data-query2.coffee b/src/data/data-query2.coffee index bb7785e..8a53648 100644 --- a/src/data/data-query2.coffee +++ b/src/data/data-query2.coffee @@ -74,10 +74,27 @@ isValidQuery = (q) -> break unless isValid {isValid: isValid, errors: errors} +expected = [ + "context" + "key" + "updatedAfter" + "firstNObs" + "lastNObs" + "obsDimension" + "history" + "attributes" + "measures" + "filters" +] + # A query for data, as defined by the SDMX RESTful API. query = class DataQuery @from: (opts) -> + if opts + for own k, v of opts + throw Error createErrorMessage([], 'data query') \ + unless k in expected context = opts?.context ? defaults.context key = opts?.key ? defaults.key attrs = opts?.attributes ? defaults.attributes diff --git a/test/data/data-query2.test.coffee b/test/data/data-query2.test.coffee index 78ec006..00b722b 100644 --- a/test/data/data-query2.test.coffee +++ b/test/data/data-query2.test.coffee @@ -45,6 +45,9 @@ describe 'SDMX 3.0 data queries', -> q.should.have.property('measures').that.equals 'all' q.should.have.property('filters').that.has.lengthOf 0 + it 'throws an exception if the input is not as expected', -> + test = -> DataQuery2.from({test: 'test'}) + should.Throw(test, Error, 'Not a valid data query') describe 'when setting the context', -> @@ -224,4 +227,3 @@ describe 'SDMX 3.0 data queries', -> it 'throws an exception if one of the filters is invalid', -> test = -> DataQuery2.from({context: 'dataflow=BIS:CBS(1.0)', filters: ['FREQ=A', '$1']}) should.Throw(test, Error, 'Not a valid data query') - From 41eaa37afd95124821967dfa8e681cf9baea6f57 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 15:01:35 +0200 Subject: [PATCH 44/47] Check that the input has the expected properties --- src/avail/availability-query2.coffee | 14 ++++++ test/avail/availability-query2.test.coffee | 12 +++-- test/index.test.coffee | 51 ++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/avail/availability-query2.coffee b/src/avail/availability-query2.coffee index d3ca088..dddffcc 100644 --- a/src/avail/availability-query2.coffee +++ b/src/avail/availability-query2.coffee @@ -60,9 +60,23 @@ isValidQuery = (q) -> break unless isValid {isValid: isValid, errors: errors} +expected = [ + "context" + "key" + "component" + "updatedAfter" + "filters" + "mode" + "references" +] + query = class AvailabilityQuery @from: (opts) -> + if opts + for own k, v of opts + throw Error createErrorMessage([], 'availability query') \ + unless k in expected context = opts?.context ? defaults.context key = opts?.key ? defaults.key filters = opts?.filters ? defaults.filters diff --git a/test/avail/availability-query2.test.coffee b/test/avail/availability-query2.test.coffee index 9aa36ee..bd89147 100644 --- a/test/avail/availability-query2.test.coffee +++ b/test/avail/availability-query2.test.coffee @@ -38,6 +38,10 @@ describe 'SDMX 3.0 availability queries', -> q.should.have.property('mode').that.equals 'exact' q.should.have.property('references').that.equals 'none' + + it 'throws an exception if the input is not as expected', -> + test = -> AvailabilityQuery2.from({test: 'test'}) + should.Throw(test, Error, 'Not a valid availability query') describe 'when setting the context', -> @@ -159,19 +163,19 @@ describe 'SDMX 3.0 availability queries', -> describe 'when setting the processing mode', -> it 'a string representing the amount of details can be passed', -> mode = AvailabilityMode.AVAILABLE - query = AvailabilityQuery2.from({flow: 'EXR', mode: mode}) + query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', mode: mode}) query.should.have.property('mode').that.equals mode it 'throws an exception if the value for mode is unknown', -> - test = -> AvailabilityQuery2.from({flow: 'EXR', mode: 'test'}) + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', mode: 'test'}) should.Throw(test, Error, 'Not a valid availability query') describe 'when setting the references', -> it 'a string representing the references to be resolved can be passed', -> refs = AvailabilityReferences.CONCEPT_SCHEME - query = AvailabilityQuery2.from({flow: 'EXR', references: refs}) + query = AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', references: refs}) query.should.have.property('references').that.equals refs it 'throws an exception if the value for references is unknown', -> - test = -> AvailabilityQuery2.from({flow: 'dataflow', references: 'ref'}) + test = -> AvailabilityQuery2.from({context: 'dataflow=BIS:CBS(1.0)', references: 'ref'}) should.Throw(test, Error, 'Not a valid availability query') \ No newline at end of file diff --git a/test/index.test.coffee b/test/index.test.coffee index ec7cf7f..b898f18 100644 --- a/test/index.test.coffee +++ b/test/index.test.coffee @@ -12,8 +12,10 @@ describe 'API', -> sdmxrest.should.have.property 'getService' sdmxrest.should.have.property('services').that.is.an 'array' sdmxrest.should.have.property 'getDataQuery' + sdmxrest.should.have.property 'getDataQuery2' sdmxrest.should.have.property 'getMetadataQuery' sdmxrest.should.have.property 'getAvailabilityQuery' + sdmxrest.should.have.property 'getAvailabilityQuery2' sdmxrest.should.have.property 'getSchemaQuery' sdmxrest.should.have.property 'getUrl' sdmxrest.should.have.property 'request' @@ -120,6 +122,33 @@ describe 'API', -> test = -> sdmxrest.getDataQuery {test: 'TEST'} should.Throw(test, Error, 'Not a valid data query') + describe 'when using getDataQuery2()', -> + + it 'offers to create a data query from properties', -> + input = { + context: 'dataflow=ECB:EXR(*)' + key: 'A..EUR.SP00.A' + } + query = sdmxrest.getDataQuery2 input + query.should.be.an 'object' + query.should.have.property('context').that.equals input.context + query.should.have.property('key').that.equals input.key + query.should.have.property('updatedAfter').that.is.undefined + query.should.have.property('firstNObs').that.is.undefined + query.should.have.property('lastNObs').that.is.undefined + query.should.have.property('obsDimension').that.is.undefined + query.should.have.property('history').that.is.false + query.should.have.property('attributes').that.equals 'dsd' + query.should.have.property('measures').that.equals 'all' + query.should.have.property('filters').that.is.instanceOf Array + query.should.have.property('filters').that.has.lengthOf 0 + + it 'fails if the input is not of the expected type', -> + t = {} + t["test"] = "test2" + test = -> sdmxrest.getDataQuery2 t + should.Throw(test, Error, 'Not a valid data query') + describe 'when using getMetadataQuery()', -> it 'offers to create a metadata query from properties', -> @@ -170,6 +199,28 @@ describe 'API', -> test = -> sdmxrest.getAvailabilityQuery {test: 'TEST'} should.Throw(test, Error, 'Not a valid availability query') + describe 'when using getAvailabilityQuery2()', -> + + it 'offers to create an availability query from properties', -> + input = { + context: 'dataflow=ECB:EXR(*)' + key: 'A..EUR.SP00.A' + } + query = sdmxrest.getAvailabilityQuery2 input + query.should.be.an 'object' + query.should.have.property('context').that.equals input.context + query.should.have.property('key').that.equals input.key + query.should.have.property('component').that.equals '*' + query.should.have.property('updatedAfter').that.is.undefined + query.should.have.property('filters').that.is.instanceOf Array + query.should.have.property('filters').that.has.lengthOf 0 + query.should.have.property('mode').that.equals 'exact' + query.should.have.property('references').that.equals 'none' + + it 'fails if the input is not of the expected type', -> + test = -> sdmxrest.getAvailabilityQuery2 {test: 'TEST'} + should.Throw(test, Error, 'Not a valid availability query') + describe 'when using getSchemaQuery()', -> it 'offers to create a schema query from properties', -> From f6d9b6bd90e079f11eaec68b9a1906b4d6679023 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 15:13:24 +0200 Subject: [PATCH 45/47] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eafb41c..a995df0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": ">=10" }, "description": "SDMX REST API client for JavaScript", - "version": "2.19.0", + "version": "2.20.0", "main": "./lib/index.js", "scripts": { "prebuild": "rm -rf lib && mkdir lib", From 1b43bd005b382f21c15492188cc63ba990101124 Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Thu, 4 Aug 2022 15:14:54 +0200 Subject: [PATCH 46/47] Apply coding conventions --- src/index.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.coffee b/src/index.coffee index 02cbd70..df5a8c7 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -197,11 +197,11 @@ getDataQuery = (input) -> # - *measures* (optional) - The measures to be returned (default: all). # - *filters* (optional) - The component filters to be applied. # -# @example Create a query for all data belonging to the CBS dataflow, +# @example Create a query for all data belonging to the CBS dataflow, # maintained by the BIS # sdmxrest.getDataQuery({context: 'dataflow=BIS:EXR(*)'}) # -# @example Create a query for EXR data, matching values A for the 1st +# @example Create a query for EXR data, matching values A for the 1st # dimension, any value for the 2nd dimension, EUR, SP00 and A for the 3rd, 4th # and 5th dimensions respectively # sdmxrest.getDataQuery({context: 'dataflow=*:EXR(*)', key: 'A..EUR.SP00.A'}) @@ -300,7 +300,7 @@ getAvailabilityQuery = (input) -> # - *mode* (optional) - the possible processing modes (default: exact) # - *references* (optional) - the references to be returned (default: none) # - *filters* (optional) - The component filters to be applied. -# +# # @example Create an availability query for the ECB EXR dataflow # sdmxrest.getAvailabilityQuery({context: 'dataflow=ECB:EXR(*)'}) # From be1b450bc3f59faca3942e2d708a75adeca9ce1a Mon Sep 17 00:00:00 2001 From: Xavier Sosnovsky Date: Fri, 5 Aug 2022 08:17:08 +0200 Subject: [PATCH 47/47] getUrl supports new query types --- src/index.coffee | 23 ++++++--- test/index.test.coffee | 115 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/index.coffee b/src/index.coffee index df5a8c7..8547468 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -361,16 +361,25 @@ getUrl = (query, service) -> throw ReferenceError 'Not a valid service' unless service throw ReferenceError 'Not a valid query' unless query s = getService service - q = if (query.mode? or \ - (query.flow? and query.references?) or \ - (query.flow? and query.component?)) + q = if query.resource? + getMetadataQuery query + else if query.context? and query.agency? + getSchemaQuery query + else if query.flow? and \ + (query.references? or query.component? \ + or query.mode?) getAvailabilityQuery query + else if query.references? or query.component? \ + or query.mode? + getAvailabilityQuery2 query else if query.flow? getDataQuery query - else if query.resource? - getMetadataQuery query - else if query.context? - getSchemaQuery query + else if query.context? or query.key? or query.filters? \ + or query.firstNObs? or query.lastNObs? or query.obsDimension? \ + or query.history? or query.attributes? or query.measures? \ + or query.updatedAfter? + getDataQuery2 query + else throw Error 'Not a valid query' unless q return new UrlGenerator().getUrl q, s diff --git a/test/index.test.coffee b/test/index.test.coffee index b898f18..893d586 100644 --- a/test/index.test.coffee +++ b/test/index.test.coffee @@ -330,6 +330,121 @@ describe 'API', -> url.should.contain 'A..EUR.SP00.A' url.should.contain 'references=all' + it 'creates a URL from an availability2 and service objects (mode)', -> + q = {mode: 'exact'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'availability/' + url.should.contain 'mode=exact' + + it 'creates a URL from an availability2 and a service objects (component)', -> + q = {component: 'FREQ'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'availability/' + url.should.contain 'FREQ' + + it 'creates a URL from an availability2 and a service objects (references)', -> + q = {references: 'all'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'availability/' + url.should.contain 'references=all' + + it 'creates a URL from a data2 query and a service objects (context)', -> + q = {'context': 'dataflow=BIS:CBS(1.0)'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/dataflow/BIS/CBS/1.0' + + it 'creates a URL from a data2 query and a service objects (key)', -> + q = {'key': 'A.CHF.EUR'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*/A.CHF.EUR' + + it 'creates a URL from a data2 query and a service objects (filters)', -> + q = {'filters': 'REF_AREA=CH'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'c[REF_AREA]=CH' + + it 'creates a URL from a data2 query and a service objects (firstNObs)', -> + q = {'firstNObs': 1} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'firstNObservations=1' + + it 'creates a URL from a data2 query and a service objects (lastnObs)', -> + q = {'lastNObs': 1} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'lastNObservations=1' + + it 'creates a URL from a data2 query and a service objects (obsDimension)', -> + q = {'obsDimension': 'CUR'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'dimensionAtObservation=CUR' + + it 'creates a URL from a data2 query and a service objects (history)', -> + q = {'history': true} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'includeHistory=true' + + it 'creates a URL from a data2 query and a service objects (attributes)', -> + q = {'attributes': 'msd'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'attributes=msd' + + it 'creates a URL from a data2 query and a service objects (measures)', -> + q = {'measures': 'none'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'measures=none' + + it 'creates a URL from a data2 query and a service objects (updatedAfter)', -> + q = {'updatedAfter': '2016-03-04T09:57:00Z'} + s = sdmxrest.getService({url: 'http://ws-entry-point'}); + url = sdmxrest.getUrl q, s + url.should.be.a 'string' + url.should.contain 'http://ws-entry-point' + url.should.contain 'data/*/*/*/*' + url.should.contain 'updatedAfter=2016-03-04T09:57:00Z' + it 'fails if the input is not of the expected type', -> test = -> sdmxrest.getUrl undefined, sdmxrest.getService 'ECB' should.Throw(test, Error, 'Not a valid query')