-
-
Notifications
You must be signed in to change notification settings - Fork 224
coerceComponents feature for middleware in order to cast data #493
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
Conversation
Sadly, doesn't seem to work for me for a In your tests, please add a case where you validate a response consisting of an object with a property of type |
Sorry for the first version. It still had a bug. I just fixed it, I modified the example to send a date.
|
Thank you so much for this PR @pilerou . Merci beaucoup! I think I understand how your code works. You can tell I've been able to make your code work in my project, but with a change on my side. In my spec file, this doesn't work:
But this does work:
In other words, you need to define a schema for the components you want to tweak, is that correct? Do you see any way to make your new feature work with my first exemple too? Don't get me wrong, I think your PR should be merged now, and I will definitively use its |
I agree with you about the perfect solution that would be to use the At the end, I convinced myself that it's finally a good practice to have a generic component type definition for Date, Date-time or ObjectId... :) The feature can solve many issues but I do agree that it's not very intuitive for beginner on this module. I didn't find a better way to do it easier. :S |
I think this is an acceptable solution (maybe even the only one)! I would suggest that there is a small section in the documentation to explain it, though. In issue #288 I said I would give a bounty for a fix so, as soon as the PR is merged, let me know what you want me to do with the money. Send me your Paypal address if you want. Thanks again! |
I didn't see for the bounty purposal. |
The money will go to the project itself then ("Sponsor" link). Cheers! |
@electrotype thank you :) very generous. once it's merged in, i will plan to reroute the full bounty to @pilerou. he did the work and deserves it. @pilerou DM me on twitter or linkedin, so we can sort out how to get it to you. |
@pilerou, we have serialize/deserialize backwards
in these scenarios, deserialization occurs on the value provided in the request e.g. a string representing a date or mongo object id, etc. serialization will occur on a response when converting an object e.g. |
review notes:
|
Thanks for the review. I change it and I push. |
I did the changes :
|
@pilerou i made modifed the types to use |
while making these modifications, another question arose. what is the behavior if a user sets |
@cdimascio All modifications are good to me. Your english is also much better than mine 👍 I see 2 options :
I prefer the first solution. What do you prefer ? |
@pilerou gotcha. in that case, it's best to just throw an error right away and let the user sort out how to proceed. To throw this error, the validation check can be done here. All in all, if either Also, do you see any way that we could provide this feature without requring There are many users who enable |
I understand. |
Thanks. I'll think on it as well. |
@all-contributors add @pilerou for code, test, and ideas |
I've put up a pull request to add @pilerou! 🎉 |
@pilerou @electrotype Thus, when the response validation runs, the On the other hand, when An easy way to solve this, is to serialize the response object prior to validation e.g. body = JSON.parse(JSON.stringify(body));
const valid = validator({
response: body,
}); This solves the problem. However, when This approache does not provide any control over the string representation. You basically get whatever new Date.prototype.toJSON = function () {
return 'my date';
}; Here is the code: #498. This doens't require mappers but has the side effect i noted above It would be nice to handle this with something like the above, but somehow avoid the double serialization. It's simpler, doesn't complicate the API/options, and the response is returned whether or not @pilerou's solution is more robust, but there are behavior differences in the way respnses are handled depending on whether |
@cdimascio This is exactly what we are trying to avoid! :-) See #288 (comment) . Yes it works, but you then se/deserialize every response, with or without dates, simply because it is required for this validation to work. The other "workaround" is for the application code itself to know that it has to convert the date properties before sending the responses. It is faster because it knows the entities so can convert specific properties without se/deserializing everything. I really do not like this solution because the application would do this simply because From what I understand of the code, the real solution would be the make a PR to If you are able to hack |
@pilerou i refactored the schema preprocessor to traverse the request and response schema in one pass. it effectively traverses one schema, but allows updates to both along the way. What's improved?This improvement makes it possible to associate a custom keyword with any schema in the spec (rather than only those we register via property like type: string
format: date-time When the preprocessor visits a schema, we can check... "does ####Benefit The PR codePR #499 implements this improved preprocessor. I'm hoping that you might fork the PR branch and integrate something similar to what you've done. One difference being that the user can define the types as usual (just specify What's Next? - How can @pilerou help?It would be awesome, if you could take the essence of what you've done and incorporate it into the preprocessor. Here's what I'm thinking For formats defined in the OpenAPI spec (
|
Good news! we can definitely do this with a single serializer. Example here using date-time format (which ultimately will be baked in) @pilerou, i'll work to get the date-time, full-date formats added as built-ins. Feel free to take a stab at mongo's objectid> This will require a user option, perhaps a good place to put these options is Note: there is a backward compatibility issue for dates that were provided as string... will think on how to ensure both can work |
Feedback left on #499 |
@electrotype have a look at #499 I'm curious to get your thoughts for the next major version. Should this be opt-in? or default behavior? Note that its essentially either or. if the serializer is enabled, then you MUST specify Happy Holidays! |
Happy Holidays to you too. First, excuse me because I know I'm not as deep into the code itself as you (both) are. I currently do not have the time to invest in understanding everything involved in this issue, as much as I would like. Sorry about that, I may try to contribute more to this project one day. I understand your question and why you are not sure about the best way to go. If I enable the
And I agree this is not the ideal. It is confusing. I think maybe the root issue of all that is that the validation is not made on what is actually sent to the client (a Date will always result in a string at the end!), but on the raw response your controllers (or whatever) are sending in the first place... My random ideas/notes:
Dates really are a tricky type in Javascript/JSON! |
all in all, we can do 2. are remove the |
@all-contributors add @electrotype for ideas |
I've put up a pull request to add @electrotype! 🎉 |
Can't wait to see the final PR! You guys are doing an incredible job... Listening to your users and such. Cheers! And don't worry about the bounty and where it will go, just make sure the solution is the best one. I will make sure everybody involved here has my official "thank you very, very much" appreciation... The hard part is sometimes simply to make things move. :-) |
@pilerou it will be fantastic if you can add the functionality for A path toward a solution might look something like this:
|
I'll be trying 4.10.0 very soon. Thank you! |
Hi. Questions
**Implementation examples : **
Suggestion
Here how I changed it in export class SchemaPreprocessor {
private ajv: Ajv;
private apiDoc: OpenAPIV3.Document;
private apiDocRes: OpenAPIV3.Document;
private responseOpts: ValidateResponseOpts;
private serializers: { [p: string]: Serializer; };
constructor(
apiDoc: OpenAPIV3.Document,
ajvOptions: ajv.Options,
validateResponsesOpts: ValidateResponseOpts,
) {
this.ajv = createRequestAjv(apiDoc, ajvOptions);
this.apiDoc = apiDoc;
this.responseOpts = validateResponsesOpts;
if(this.responseOpts && this.responseOpts.serializers)
this.serializers = {
...basicSerializer,
...this.responseOpts.serializers
}
} const basicSerializer = {
'date-time': {
serialize: (d: Date) => {
return d && d.toISOString();
},
},
'date': {
format: 'date',
serialize: (d: Date) => {
return d && d.toISOString().split('T')[0];
},
}
}; private handleSerDes(
parent: SchemaObject,
schema: SchemaObject,
state: TraversalState,
) {
if (state.kind === 'res') {
if (schema.type === 'string' && !!schema.format && this.serializers[schema.format]) {
schema['x-eov-serializer'] = this.serializers[schema.format];
}
}
} |
Ki@pilerou
The new feature does not affect the request at all, thus
This is currently the case. The options are basically validateRequests: [{
format: 'mongodb-objectid':
deserializer: (s) => ObjectId(s)
}] vs serdes: [{
format: 'mongodb-objectid',
deserializer: (s) => ObjectId(s),
}] for a user with both request and response validation enabled validateRequests: [{
format: 'mongodb-objectid',
deserializer: (o) => o.toString()
}]
validateReponses: [{
format: 'mongodb-objectid',
serializer: (o) => o.toString()
}] vs serdes: [{
format: 'mongodb-objectid',
serializer: (o) => o.toString(),
deserializer: (s) => ObjectId(s),
}] Okay, @pilerou I'm with you, option 2 is better :) |
@pilerou @electrotype Another thought to consider... for requests:express does not perform any special deserialization of requests. For example, if you provide a string representation of a for responses:similarly, express does not perform any complex serialization, it just calls Next stepsPerhaps, we should start by solving this problem. That is, when enabling This would be a great first step. As we've dive deep into these discussions, to me, it seems the custom By implementing this as a first step, a user would just list the format, then e.g. Implementation thoughtsSpittballing here, but this first step could be implemented by adding the type, ObjectId could be declared in the spec as type: string
format: mongodb-objectid Then, the preprocessor would transform the schema to type: ['string', 'object']
format: mongodb-objectid The serialization could be applied anytime the This benefit here is that no new options are necessary. Thoughts? |
@pilerou I think the decision made by @cdimascio is the good one. Modifications were quite simple and they address one use case perfectly. The scope of what you suggest is larger and, probably for some, more complete. Anyway, I'm very happy with the developments you both made, thank you again. Since you do not want the bounty (or do you? Let me know if you changed your mind), can I ask you to pick a charity? I will send them the money and will post a proof here. Good luck with your own use cases and with the continuation of those features! |
Thanks for your responses. I also think you made the good solution. I work on request mecanism and other custom formats and I make a new proposition.
|
It may be simplest to start a new fork. That said, the PR that I merged in is based on your original work, thus, you could merge master into this PR's branch. It likely won't run into conflicts. Either way will work. Up to u. |
@pilerou Without a suggestion from you, I'm going to give the money to some charity that saves giraffes in Alaska or something. |
Thanks @cdimascio. I do that. |
https://www.buymeacoffee.com/m97tA5c/c/631986 |
This pull request adds an attribute to middleware configuration :
coerceComponents
.It allows to cast :
I added an example project and a suggestion for README.MD