-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Delete BaseHTTPService and implement new BaseXmlService (affects [eclipse-marketplace f-droid]; also testing on [uptimerobot circleci]) #2037
Conversation
Eclipse Markteplace badges were migrated as an example
Generated by 🚫 dangerJS |
lib/response-parsers.js
Outdated
|
||
async function asXml({ buffer, res }) { | ||
let xml | ||
xml2js.parseString(buffer, (err, parsedData) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This uses a callback and works fine because the underlying implementation is not async (see Leonidas-from-XIV/node-xml2js#159 (comment)), but there is probably a better way of doing this. Any ideas/guidance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aw, that is a sadly unpleasant thread.
Two thoughts:
- For the new services, we could adopt the parser mentioned here, which is synchronous: Sync version of parseString Leonidas-from-XIV/node-xml2js#159 (comment) They say it's faster.
- You could wrap the callback function in a promise using
util.promisify
:
const { promisify } = require('util')
const parseXml = promisify(require('xml2js').parseString)
let xml
try {
// `parseXml` is returning an awaitable promise so if you were assigning its
// return value to a variable, you'd need `await`. However on a return from
// an async function, there's no need to specify `await`.
return parseXml(buffer)
} catch (err) {
throw …
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tied solution 1, switching to fast-xml-parser
.
Good points:
- cleaner code in response-parser.js.
xml2js
has a tendency of introducing arrays everywhere even when not necessary. Switching has simplified the Joi schemas and the constructed JS objects.- claimed to be faster.
Bad point:
no helpful exception message if the XML is not parsable.
I ran npm i fast-xml-parser
to update package-lock.json, is this the correct approach? A huge diff is generated on the file.
Badges generated for my plugin Notepad4e, using the Heroku review app: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. Nice work! I like how you moved _validate
to the base class, too.
I added [uptimerobot circleci] to the PR title – which are just a couple examples of services that use BaseJsonService, which was also affected by this change.
lib/response-parsers.js
Outdated
'use strict' | ||
|
||
const { InvalidResponse } = require('../services/errors') | ||
const xml2js = require('xml2js') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're only using this code in one place. Would it be more direct to move it there, inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With fast-xml-parser
, the parser is now used in two places, so keeping the require
up there. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, I meant the functions themselves, not the require
s. Instead of having these in a separate module, could they just be inlined into the _requestX
methods in the base services?
lib/response-parsers.js
Outdated
|
||
async function asXml({ buffer, res }) { | ||
let xml | ||
xml2js.parseString(buffer, (err, parsedData) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aw, that is a sadly unpleasant thread.
Two thoughts:
- For the new services, we could adopt the parser mentioned here, which is synchronous: Sync version of parseString Leonidas-from-XIV/node-xml2js#159 (comment) They say it's faster.
- You could wrap the callback function in a promise using
util.promisify
:
const { promisify } = require('util')
const parseXml = promisify(require('xml2js').parseString)
let xml
try {
// `parseXml` is returning an awaitable promise so if you were assigning its
// return value to a variable, you'd need `await`. However on a return from
// an async function, there's no need to specify `await`.
return parseXml(buffer)
} catch (err) {
throw …
}
lib/response-parsers.spec.js
Outdated
expect(e.prettyMessage).to.equal('unparseable xml response') | ||
} | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding these tests! Well worth keeping and adjusting if this functionality is moved inline, which would widen the bracket a bit.
We aren't using nock
directly anywhere right now, though it's probably a good idea to start. I've written service tests for things that ought to be unit tests, and smaller-bracket unit tests for things, e.g. the one in #2036, which probably should mock an HTTP response.
services/base-json.js
Outdated
logTrace(emojic.dart, 'Response JSON (before validation)', json, { | ||
deep: true, | ||
}) | ||
return this.constructor._validate(json, schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is much clearer!
services/base-xml.spec.js
Outdated
expect(serviceData).to.deep.equal({ | ||
message: 'some-string', | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a third test, of valid XML which fails the schema, to establish that _validate
is wired up correctly?
|
||
async handle({ name }) { | ||
const { marketplace } = await this.fetch({ name, schema }) | ||
const downloads = base.endsWith('dt') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be slightly clearer to condition on interval
here.
@@ -0,0 +1,38 @@ | |||
'use strict' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were there any changes in the tests, or were they just moved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The five existing tests we copy-pasted as is in the different tester classes. Additionally, I added one "not found" test case for each implementation.
) | ||
return value | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the idea of moving _validate
here. Nice work!
logTrace(emojic.bowAndArrow, 'Request', url, '\n', options) | ||
const { res, buffer } = await this._sendAndCacheRequest(url, options) | ||
logTrace(emojic.dart, 'Response status code', res.statusCode) | ||
return checkErrorResponse.asPromise(errorMessages)({ buffer, res }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT the only difference between json and xml is the parsing – is that right?
Another way we could consider handling the relationship between _request
and _validate
is to have _request
delegate to a _parse
function, and put the format-specific bits there. The service code would invoke _request
which would be BaseService's implementation, and that would in turn invoke _parse
which would be BaseXxService's.
This would force me to change slightly the way I handled #2031 because every service would then need a _parse
implementation. It would also generally force everything to be a native object that can be checked by a schema. However I don't see a problem with that. We could make a BaseSvgBadge subclass easily enough. And encouraging schema validation is good.
I like this idea because the _validate
function is in BaseService so it would feel natural to call it from its _request
function. The logging code probably could be moved from BaseXxService to BaseService as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requests are slightly different as well, in particular the Accept
headers. If we go with your suggestion, we'll also have to add a get defaultHeaders
function or something like that, which we would override in the implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, the request options, that's a good point. The headers could also be added in a _request
override. Though the differences in the requests may mean it's clearer to handle this the way it is.
Conflicts: services/base-json.js services/base-json.spec.js
services/base.js
Outdated
error.message | ||
) | ||
throw new InvalidResponse({ | ||
prettyMessage: 'invalid data response', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about invalid response data?
package-lock.json
Outdated
@@ -10,7 +10,7 @@ | |||
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", | |||
"dev": true, | |||
"requires": { | |||
"@babel/highlight": "^7.0.0" | |||
"@babel/highlight": "7.0.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This package-lock diff is unusually large. Could you make sure you're on the latest npm, reset this file, and then run npm install
again to see if that tightens it up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, updating to the latest version of npm
seems to have helped a lot. 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified one (prios) suggestion, though happy to defer that, too! This looks great!
@paulmelnikow I've tried inlining the code as per your suggestion, but I think I'm getting confused:
All JSON tests have started failing with |
Does |
Ah, no, I think it returns |
Indeed, I was getting confused with the return types, thanks for the help. This should now be ready to go! 👍 |
There's quite a few things going on here:
_request
function to the BaseService class. Tests were added for this method._validate
was also pushed up to BaseService, as this allowed to easily implement BaseXmlService alongside the existing BaseJsonService class with very little code duplication.asJson
was pulled out of error-helpers.js and now sits in response-parsers.js with a new friend,asXml
. These functions are now properly tested.I'll launch a Heroku review app so we can play around with the migrated Eclipse badges and make sure we haven't missed anything! 👍