diff --git a/lib/request.js b/lib/request.js index 0eccbb52..1aefd2b4 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,13 +1,12 @@ 'use strict' +let captureStackTrace = () => {} +if (process.env.NODE_ENV === 'dev') { + captureStackTrace = require('capture-stack-trace') +} +const {stringify} = require('query-string') const Promise = require('pinkie-promise') const {fetch} = require('fetch-ponyfill')({Promise}) -const {stringify} = require('query-string') - -const hafasError = (err) => { - err.isHafasError = true - return err -} const request = (profile, data) => { const body = profile.transformReqBody({lang: 'en', svcReqL: [data]}) @@ -24,20 +23,33 @@ const request = (profile, data) => { }) const url = profile.endpoint + (req.query ? '?' + stringify(req.query) : '') + // Async stack traces are not supported everywhere yet, so we create our own. + const err = new Error() + err.isHafasError = true + err.request = body + captureStackTrace(err) + return fetch(url, req) .then((res) => { + err.statusCode = res.status if (!res.ok) { - const err = new Error(res.statusText) - err.statusCode = res.status - throw hafasError(err) + err.message = res.statusText + throw err } return res.json() }) .then((b) => { - if (b.err) throw hafasError(new Error(b.err)) - if (!b.svcResL || !b.svcResL[0]) throw new Error('invalid response') + if (b.err) { + err.message = b.err + throw err + } + if (!b.svcResL || !b.svcResL[0]) { + err.message = 'invalid response' + throw err + } if (b.svcResL[0].err !== 'OK') { - throw hafasError(new Error(b.svcResL[0].errTxt)) + err.message = b.svcResL[0].errTxt + throw err } const d = b.svcResL[0].res const c = d.common || {} diff --git a/p/vbb/index.js b/p/vbb/index.js index 2c9325c6..a3fb5a94 100644 --- a/p/vbb/index.js +++ b/p/vbb/index.js @@ -63,7 +63,7 @@ const parseLocation = (profile, l) => { res.id = to12Digit(res.id) if (!res.location.latitude || !res.location.longitude) { const [s] = getStations(res.id) - if (s) Object.assign(res.location, s.coordinates) + if (s) Object.assign(res.location, s.location) } } return res diff --git a/package.json b/package.json index 1c4087fd..63a7beb0 100644 --- a/package.json +++ b/package.json @@ -31,20 +31,20 @@ "node": ">=6" }, "dependencies": { + "capture-stack-trace": "^1.0.0", "fetch-ponyfill": "^4.1.0", "lodash": "^4.17.4", - "luxon": "^0.2.11", + "luxon": "^0.3.1", "pinkie-promise": "^2.0.1", "query-string": "^5.0.0", "slugg": "^1.2.0", - "vbb-parse-line": "^0.2.5", + "vbb-parse-line": "^0.3.0", "vbb-parse-ticket": "^0.2.1", "vbb-short-station-name": "^0.4.0", - "vbb-stations": "^5.9.0", + "vbb-stations": "^6.1.0", "vbb-translate-ids": "^3.1.0" }, "devDependencies": { - "co": "^4.6.0", "db-stations": "^1.34.0", "is-coordinates": "^2.0.2", "is-roughly-equal": "^0.1.0", @@ -52,10 +52,10 @@ "tape": "^4.8.0", "tape-promise": "^2.0.1", "validate-fptf": "^1.2.0", - "vbb-stations-autocomplete": "^2.11.0" + "vbb-stations-autocomplete": "^3.0.0" }, "scripts": { - "test": "node test/index.js", + "test": "env NODE_ENV=dev node test/index.js", "prepublishOnly": "npm test | tap-spec" } } diff --git a/parse/departure.js b/parse/departure.js index a4fce0dd..e8f2d9bf 100644 --- a/parse/departure.js +++ b/parse/departure.js @@ -20,7 +20,7 @@ const createParseDeparture = (profile, stations, lines, remarks) => { remarks: d.remL ? d.remL.map(findRemark) : [], trip: +d.jid.split('|')[1] // todo: this seems brittle } - // todo: res.trip from rawLine.prodCtx.num + // todo: res.trip from rawLine.prodCtx.num? if (d.stbStop.dTimeR && d.stbStop.dTimeS) { const realtime = profile.parseDateTime(profile, d.date, d.stbStop.dTimeR) diff --git a/parse/line.js b/parse/line.js index 47333a14..79300c08 100644 --- a/parse/line.js +++ b/parse/line.js @@ -12,12 +12,11 @@ const createParseLine = (profile, operators) => { name: p.line || p.name, public: true } + // todo: what is p.prodCtx && p.prodCtx.num? - // We don't get a proper line id from the API, so we use the trip nr here. - // todo: find a better way - if (p.prodCtx && p.prodCtx.num) res.id = p.prodCtx.num // This is terrible, but FPTF demands an ID. Let's pray for VBB to expose an ID. - else if (p.line) res.id = slugg(p.line.trim()) + // todo: find a better way + if (p.line) res.id = slugg(p.line.trim()) else if (p.name) res.id = slugg(p.name.trim()) if (p.cls) res.class = p.cls diff --git a/readme.md b/readme.md index 8bcba40f..e27e8d71 100644 --- a/readme.md +++ b/readme.md @@ -4,8 +4,8 @@ HAFAS endpoint | wrapper library? | docs | example code | source code ---------------|------------------|------|---------|------------ -[Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas), which has additional features | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js) -[Berlin & Brandenburg public transport](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`db-hafas`](https://github.com/derhuerst/db-hafas), which has additional features | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js) +[Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas), which has additional features | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js) +[Berlin & Brandenburg public transport](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas), which has additional features | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js) [Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | – | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js) [![npm version](https://img.shields.io/npm/v/hafas-client.svg)](https://www.npmjs.com/package/hafas-client) diff --git a/test/co.js b/test/co.js new file mode 100644 index 00000000..595deb14 --- /dev/null +++ b/test/co.js @@ -0,0 +1,31 @@ +// https://github.com/babel/babel/blob/3c8d831fe41f502cbe2459a271d19c7329ffe369/packages/babel-helpers/src/helpers.js#L242-L270 +const co = (fn) => { + return function run () { + const self = this, args = arguments + return new Promise((resolve, reject) => { + const gen = fn.apply(self, args) + const step = (key, arg) => { + try { + var info = gen[key](arg) + var value = info.value + } catch (error) { + reject(error) + return + } + if (info.done) resolve(value) + else Promise.resolve(value).then(_next, _throw) + } + + const _next = (value) => { + step('next', value) + } + const _throw = (err) => { + step('throw', err) + } + + _next() + }) + } +} + +module.exports = co diff --git a/test/db.js b/test/db.js index 4fe749db..8c04637c 100644 --- a/test/db.js +++ b/test/db.js @@ -3,9 +3,9 @@ const getStations = require('db-stations').full const tapePromise = require('tape-promise').default const tape = require('tape') -const co = require('co') const isRoughlyEqual = require('is-roughly-equal') +const co = require('./co') const createClient = require('..') const dbProfile = require('../p/db') const modes = require('../p/db/modes') @@ -92,7 +92,7 @@ const assertValidPrice = (t, p) => { const test = tapePromise(tape) const client = createClient(dbProfile) -test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { +test('Berlin Jungfernheide to München Hbf', co(function* (t) { const journeys = yield client.journeys('8011167', '8000261', { when, passedStations: true }) @@ -151,7 +151,7 @@ test('Berlin Jungfernheide to München Hbf', co.wrap(function* (t) { t.end() })) -test('Berlin Jungfernheide to Torfstraße 17', co.wrap(function* (t) { +test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) { const journeys = yield client.journeys('8011167', { type: 'location', address: 'Torfstraße 17', latitude: 52.5416823, longitude: 13.3491223 @@ -180,7 +180,7 @@ test('Berlin Jungfernheide to Torfstraße 17', co.wrap(function* (t) { t.end() })) -test('Berlin Jungfernheide to ATZE Musiktheater', co.wrap(function* (t) { +test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) { const journeys = yield client.journeys('8011167', { type: 'location', id: '991598902', name: 'ATZE Musiktheater', latitude: 52.542417, longitude: 13.350437 @@ -209,7 +209,7 @@ test('Berlin Jungfernheide to ATZE Musiktheater', co.wrap(function* (t) { t.end() })) -test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co.wrap(function* (t) { +test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t) { const berlinHbf = '8011160' const münchenHbf = '8000261' const hannoverHbf = '8000152' @@ -228,7 +228,7 @@ test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co.wrap(functio t.end() })) -test('departures at Berlin Jungfernheide', co.wrap(function* (t) { +test('departures at Berlin Jungfernheide', co(function* (t) { const deps = yield client.departures('8011167', { duration: 5, when }) @@ -247,7 +247,7 @@ test('departures at Berlin Jungfernheide', co.wrap(function* (t) { t.end() })) -test('departures with station object', co.wrap(function* (t) { +test('departures with station object', co(function* (t) { yield client.departures({ type: 'station', id: '8011167', @@ -263,7 +263,7 @@ test('departures with station object', co.wrap(function* (t) { t.end() })) -test('nearby Berlin Jungfernheide', co.wrap(function* (t) { +test('nearby Berlin Jungfernheide', co(function* (t) { const nearby = yield client.nearby({ type: 'location', latitude: 52.530273, @@ -287,7 +287,7 @@ test('nearby Berlin Jungfernheide', co.wrap(function* (t) { t.end() })) -test('locations named Jungfernheide', co.wrap(function* (t) { +test('locations named Jungfernheide', co(function* (t) { const locations = yield client.locations('Jungfernheide', { results: 10 }) diff --git a/test/oebb.js b/test/oebb.js index 787eae15..3b0fdfea 100644 --- a/test/oebb.js +++ b/test/oebb.js @@ -4,12 +4,12 @@ // const getStations = require('db-stations').full const tapePromise = require('tape-promise').default const tape = require('tape') -const co = require('co') const isRoughlyEqual = require('is-roughly-equal') const validateFptf = require('validate-fptf') const validateLineWithoutMode = require('./validate-line-without-mode') +const co = require('./co') const createClient = require('..') const oebbProfile = require('../p/oebb') const products = require('../p/oebb/products') @@ -110,7 +110,7 @@ const assertValidLine = (t, l) => { // with optional mode const test = tapePromise(tape) const client = createClient(oebbProfile) -test('Salzburg Hbf to Wien Westbahnhof', co.wrap(function* (t) { +test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) { const salzburgHbf = '8100002' const wienWestbahnhof = '1291501' const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, { @@ -175,7 +175,7 @@ test('Salzburg Hbf to Wien Westbahnhof', co.wrap(function* (t) { t.end() })) -test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co.wrap(function* (t) { +test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co(function* (t) { const salzburgHbf = '8100002' const wagramerStr = { type: 'location', @@ -213,7 +213,7 @@ test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co.wrap(function* (t) { t.end() })) -test('Albertina to Salzburg Hbf', co.wrap(function* (t) { +test('Albertina to Salzburg Hbf', co(function* (t) { const albertina = { type: 'location', latitude: 48.204699, @@ -252,7 +252,7 @@ test('Albertina to Salzburg Hbf', co.wrap(function* (t) { t.end() })) -test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co.wrap(function* (t) { +test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) { const wien = '1190100' const klagenfurtHbf = '8100085' const salzburgHbf = '8100002' @@ -261,17 +261,17 @@ test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co.wrap(function* ( results: 1 }) - const i = journey.legs.findIndex(leg => leg.destination.id === salzburgHbf) - t.ok(i >= 0, 'no leg with Hannover Hbf as destination') + const i1 = journey.legs.findIndex(leg => leg.destination.id === salzburgHbf) + t.ok(i1 >= 0, 'no leg with Salzburg Hbf as destination') - const nextLeg = journey.legs[i + 1] - t.ok(nextLeg) - t.equal(nextLeg.origin.id, salzburgHbf) + const i2 = journey.legs.findIndex(leg => leg.origin.id === salzburgHbf) + t.ok(i2 >= 0, 'no leg with Salzburg Hbf as origin') + t.ok(i2 > i1, 'leg with Salzburg Hbf as origin must be after leg to it') t.end() })) -test('leg details for Wien Westbahnhof to München Hbf', co.wrap(function* (t) { +test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) { const wienWestbahnhof = '1291501' const muenchenHbf = '8000261' const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, { @@ -297,7 +297,7 @@ test('leg details for Wien Westbahnhof to München Hbf', co.wrap(function* (t) { t.end() })) -test('departures at Salzburg Hbf', co.wrap(function* (t) { +test('departures at Salzburg Hbf', co(function* (t) { const salzburgHbf = '8100002' const deps = yield client.departures(salzburgHbf, { duration: 5, when @@ -318,7 +318,7 @@ test('departures at Salzburg Hbf', co.wrap(function* (t) { t.end() })) -test('nearby Salzburg Hbf', co.wrap(function* (t) { +test('nearby Salzburg Hbf', co(function* (t) { const salzburgHbfPosition = { type: 'location', longitude: 13.045604, @@ -343,7 +343,7 @@ test('nearby Salzburg Hbf', co.wrap(function* (t) { t.end() })) -test('locations named Salzburg', co.wrap(function* (t) { +test('locations named Salzburg', co(function* (t) { const locations = yield client.locations('Salzburg', { results: 10 }) @@ -361,7 +361,7 @@ test('locations named Salzburg', co.wrap(function* (t) { t.end() })) -test('radar Salzburg', co.wrap(function* (t) { +test('radar Salzburg', co(function* (t) { const vehicles = yield client.radar(47.827203, 13.001261, 47.773278, 13.07562, { duration: 5 * 60, when }) diff --git a/test/vbb.js b/test/vbb.js index d04edfb3..bb271361 100644 --- a/test/vbb.js +++ b/test/vbb.js @@ -5,9 +5,9 @@ const isRoughlyEqual = require('is-roughly-equal') const stations = require('vbb-stations-autocomplete') const tapePromise = require('tape-promise').default const tape = require('tape') -const co = require('co') const shorten = require('vbb-short-station-name') +const co = require('./co') const createClient = require('..') const vbbProfile = require('../p/vbb') const { @@ -49,8 +49,7 @@ const assertValidLine = (t, l) => { if (l.night !== null) t.equal(typeof l.night, 'boolean') } -// todo -const findStation = (query) => stations(query, true, false) +const findStation = (query) => stations(query, true, false)[0] const test = tapePromise(tape) const client = createClient(vbbProfile) @@ -59,7 +58,7 @@ const amrumerStr = '900000009101' const spichernstr = '900000042101' const bismarckstr = '900000024201' -test('journeys – station to station', co.wrap(function* (t) { +test('journeys – station to station', co(function* (t) { const journeys = yield client.journeys(spichernstr, amrumerStr, { results: 3, when, passedStations: true }) @@ -97,7 +96,11 @@ test('journeys – station to station', co.wrap(function* (t) { assertValidWhen(t, leg.arrival, when) assertValidLine(t, leg.line) - t.ok(findStation(leg.direction)) + if (!findStation(leg.direction)) { + const err = new Error('unknown direction: ' + leg.direction) + err.stack = err.stack.split('\n').slice(0, 2).join('\n') + console.error(err) + } t.ok(leg.direction.indexOf('(Berlin)') === -1) t.ok(Array.isArray(leg.passed)) @@ -112,7 +115,7 @@ test('journeys – station to station', co.wrap(function* (t) { t.end() })) -test('journeys – only subway', co.wrap(function* (t) { +test('journeys – only subway', co(function* (t) { const journeys = yield client.journeys(spichernstr, bismarckstr, { results: 20, when, products: { @@ -141,7 +144,7 @@ test('journeys – only subway', co.wrap(function* (t) { t.end() })) -test('journeys – fails with no product', co.wrap(function* (t) { +test('journeys – fails with no product', co(function* (t) { try { yield client.journeys(spichernstr, bismarckstr, { when, @@ -161,7 +164,7 @@ test('journeys – fails with no product', co.wrap(function* (t) { } })) -test('journey leg details', co.wrap(function* (t) { +test('journey leg details', co(function* (t) { const journeys = yield client.journeys(spichernstr, amrumerStr, { results: 1, when }) @@ -187,7 +190,7 @@ test('journey leg details', co.wrap(function* (t) { -test('journeys – station to address', co.wrap(function* (t) { +test('journeys – station to address', co(function* (t) { const journeys = yield client.journeys(spichernstr, { type: 'location', address: 'Torfstraße 17', latitude: 52.5416823, longitude: 13.3491223 @@ -214,7 +217,7 @@ test('journeys – station to address', co.wrap(function* (t) { -test('journeys – station to POI', co.wrap(function* (t) { +test('journeys – station to POI', co(function* (t) { const journeys = yield client.journeys(spichernstr, { type: 'location', id: '9980720', name: 'ATZE Musiktheater', latitude: 52.543333, longitude: 13.351686 @@ -241,7 +244,7 @@ test('journeys – station to POI', co.wrap(function* (t) { -test('journeys – with stopover', co.wrap(function* (t) { +test('journeys – with stopover', co(function* (t) { const halleschesTor = '900000012103' const leopoldplatz = '900000009102' const [journey] = yield client.journeys(spichernstr, halleschesTor, { @@ -261,7 +264,7 @@ test('journeys – with stopover', co.wrap(function* (t) { -test('departures', co.wrap(function* (t) { +test('departures', co(function* (t) { const deps = yield client.departures(spichernstr, {duration: 5, when}) t.ok(Array.isArray(deps)) @@ -276,13 +279,17 @@ test('departures', co.wrap(function* (t) { t.strictEqual(dep.station.id, spichernstr) assertValidWhen(t, dep.when, when) - t.ok(findStation(dep.direction)) + if (!findStation(dep.direction)) { + const err = new Error('unknown direction: ' + dep.direction) + err.stack = err.stack.split('\n').slice(0, 2).join('\n') + console.error(err) + } assertValidLine(t, dep.line) } t.end() })) -test('departures with station object', co.wrap(function* (t) { +test('departures with station object', co(function* (t) { yield client.departures({ type: 'station', id: spichernstr, @@ -298,7 +305,7 @@ test('departures with station object', co.wrap(function* (t) { t.end() })) -test('departures at 7-digit station', co.wrap(function* (t) { +test('departures at 7-digit station', co(function* (t) { const eisenach = '8010097' // see derhuerst/vbb-hafas#22 yield client.departures(eisenach, {when}) t.pass('did not fail') @@ -308,7 +315,7 @@ test('departures at 7-digit station', co.wrap(function* (t) { -test('nearby', co.wrap(function* (t) { +test('nearby', co(function* (t) { // Berliner Str./Bundesallee const nearby = yield client.nearby({ type: 'location', @@ -337,7 +344,7 @@ test('nearby', co.wrap(function* (t) { -test('locations', co.wrap(function* (t) { +test('locations', co(function* (t) { const locations = yield client.locations('Alexanderplatz', {results: 10}) t.ok(Array.isArray(locations)) @@ -356,7 +363,7 @@ test('locations', co.wrap(function* (t) { -test('radar', co.wrap(function* (t) { +test('radar', co(function* (t) { const vehicles = yield client.radar(52.52411, 13.41002, 52.51942, 13.41709, { duration: 5 * 60, when }) @@ -365,7 +372,11 @@ test('radar', co.wrap(function* (t) { t.ok(vehicles.length > 0) for (let v of vehicles) { - t.ok(findStation(v.direction)) + if (!findStation(v.direction)) { + const err = new Error('unknown direction: ' + v.direction) + err.stack = err.stack.split('\n').slice(0, 2).join('\n') + console.error(err) + } assertValidLine(t, v.line) t.equal(typeof v.location.latitude, 'number')