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
Feature : options.validate integration with add
#165
Conversation
Hey @gr2m |
|
||
.then(function () { | ||
return internals.put(state, internals.addTimestamps(doc)) |
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.
Btw, why isn't internals
API like internals.put(state, doc)
?
Because internals.addTimestamps
is accessible inside the definition of internals.put
as this.addTimestamps
. Am I wrong about this?
Just a quick note that I won’t be able to look into this before next week :( |
index.js
Outdated
@@ -33,6 +33,7 @@ function Store (dbName, options) { | |||
dbName: dbName, | |||
PouchDB: options.PouchDB, | |||
emitter: emitter, | |||
validate: options.validate || function () { return true }, |
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 could make this a simple no-op: function () {}
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.
or maybe we should leave it undefined, see my comment below
lib/validate.js
Outdated
var Promise = require('lie') | ||
|
||
function validate (state, doc) { | ||
var self = this |
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.
If we would leave state.validate unset if the options wasn’t set in the constructor, then we could check if it is set on this line and if it’s not directly return Promise.resolve()
lib/validate.js
Outdated
reject(error) | ||
} | ||
|
||
resolve(doc) |
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 think what I would do is this:
return Promise.resolve()
.then(function () {
return state.validate(doc)
})
.catch(function (error) {
error.name = 'ValidationError'
throw error
})
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 won't take into account whether the validation was successful or not
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 would have worked if the validation was supposed to be triggered manually, but we are coupling it with CRUD methods isn't it?
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.
sorry I don’t quite understand?
The validate method can, but does not need to be asynchronous. If it throws or returns a rejected promise, the validation will fail, otherwise it will succeed.
wether or not the validate method in your app sends requests to the server (is that what you mean with CRUD methods), is totally up to you
lib/validate.js
Outdated
var result | ||
|
||
try { | ||
result = state.validate.call(self, doc) |
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 don’t think you need need to bind validate to self
here, we try to avoid using this
altogether in our codebase
Hey @gr2m ! |
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.
hey just a few comments regarding tests, could you have a look at these? I feel bad because the PR is great and it’s going for quite long, if you want I can also merge it as is and we add more tests later?
tests/integration/add.js
Outdated
var store = new Store(name, { | ||
PouchDB: PouchDB, | ||
remote: 'remote-' + name, | ||
validate: function () { throw new Error() } |
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.
can you pass in an error message here
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.
Will do. But, how does it impact our tests?
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.
you can check if the error message you throw arrives in the .catch(error) {}
, see my comment below: #165 (comment)
t.fail('Expecting ValidationError') | ||
}) | ||
.catch(function (error) { | ||
t.is(error.name, 'ValidationError') |
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.
can you check for the message thrown above?
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.
message would be undefined
or an empty string (don't know for sure) in this implementation as the mock validate
method throws new Error()
}, { | ||
_id: 'foo', | ||
foo: 'baz' | ||
}]) |
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 think we can simplify the test here and only create one document.
But it would be interesting to see what happens when we try creating two documents at once, one passes validation and one fails. Could you add a test for that maybe?
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 test is specifically for multiple additions.
As for your question, my code will not add any doc unless all the docs pass validation. Let me know if you'd want that behaviour to change?
@gr2m I'm okay with giving this PR time. No reason to hurry and merge code :) |
Sounds like a plan to me :) very good! |
The current status of the PR has hooked-up validation flow with document addition, with the following behaviour :
I believe the tests I have added/modified are good enough to the best of my understanding. Do let me know if you'd like to change some behaviour of this functionality @gr2m ? |
Another thing I missed until now, I’m so sorry. Can you add a test where the validation method returns a rejected Promise? It should fail in that case, too. If it rejects without an error message, we should maybe add a default message? What do you think? |
A rejected Promise will be caught by This is because a Promise can reject with any value (and not just an Error object). It makes much more sense to keep our code resilient to such cases, hence, I'll be taking the following steps in our
So, for example // someObject is a doc which will fail validation
store.add(someObject)
.then(someFunction)
.catch(function (error) {
// if our validation rejected with value 'false'
console.log(error.name) // ValidationError
console.log(error.message) // check error value for more details
console.log(`validation evaluated as ${error.value}`) // validation evaluated as false
}) Does this make sense? |
yes, that all makes sense 👍 Maybe one addition we can consider to
if the passed argument is a string, then set |
I was intending to do the same, but got side-tracked. Thanks for catching that 😛 |
lib/validate.js
Outdated
@@ -13,7 +13,24 @@ function validate (state, doc) { | |||
return state.validate(doc) | |||
}) | |||
.catch(function (error) { | |||
error.name = 'ValidationError' | |||
throw error | |||
var err = new Error() |
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.
what do you think about renaming the passed argument from error
to rejectValue
and var err
to var error
? I try to avoid abbreviations as much as possible as a general rule
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.
Isn't it misleading to make the argument name rejectValue
, considering it can be an error throw directly as well? Although, I can't think of any other name better suited to it 😕
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.
technically a thrown error is captured by a promise and turned into the value that the promise rejects with :)
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.
Dayum. This task has been the best on learning how Promises work 😛
add
lib/validate.js
Outdated
} else { | ||
err.message = error.message | ||
error.message = rejectValue.message | ||
} | ||
} |
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 would sup around the if / else statements. Try avoiding negations. E.g. do if (rejectValue instanceof Error) {
instead of if (!(rejectValue instanceof Error)) {
and if (rejectValue.message) {
instead of if (!rejectValue.message) {
I was also thinking that maybe people will throw errors with custom data on it, e.g. think a request error that fails with a .status
or .statusCode
property. These would get lost in this code.
What we could do is that if rejectValue
is instanceof Error
, we can directly edit that errors .name
and defaults its .message
property to your doc(s) failed validation
?
🤔
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 did some local edits and turns out the error we throw is still represented as Error: <message>
rather than ValidationError: <message>
Now, I was hoping that it would work as I was avoiding creating a custom Error as it requires the use of this
.
I know hoodie tries to avoid using this
in the code, so what do you propose we do about this?
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.
^ @gr2m
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 is odd 🤔 It should ™️ be possible to change an error’s name:
const error = new Error('test')
error.name = 'ValidationError'
error.toString()
// ValidationError: test
I don’t see how we would need to use this
here? Sorry I must miss something obvious :(
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.
Try throwing this error. You'll see the difference :)
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 I didn’t know you are right
const error = new Error('test')
error.name = 'ValidationError'
throw error
// Unncaught Error: test
But try this
error = new Error('test')
error.name = 'ValidationError'
Promise.resolve()
.then(() => {throw error})
.catch((error) => console.log(error.toString()))
// logs ValidationError: test
So my guess is it should work?
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.
Need a slight tweak to what you propose...
error = new Error('test')
error.name = 'ValidationError'
Promise.resolve()
.then(() => {throw error})
.catch((error) => {
console.error(error.toString())
return Promise.reject()
})
Let me know if we can finalize this, @gr2m
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.
My code was just an example to showcase that the custom error name works. I think we should be good now, if you could finalize it and test the custom error name in a test, that’d be great 👍
lib/validate.js
Outdated
if (!error.message) { | ||
err.message = 'your doc(s) failed validation' | ||
if (!rejectValue.message) { | ||
error.message = 'your doc(s) failed validation' |
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 would phrase the default error message without using a pronoun. Maybe something like document validation failed
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.
@mbad0la can you make that change?
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.
Sure, I'm just waiting on us to resolve how to go about Error creation/mapping. This is pretty straight-forward to fix.
Hey @gr2m |
tests/unit/add-one-test.js
Outdated
test('add-one validation fails with custom error', function (t) { | ||
t.plan(4) | ||
|
||
let customError = new Error('custom error message') |
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 still do only use var
right now and don’t use any other es2016 features at this point
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.
that’s why the build is failing: https://travis-ci.org/hoodiehq/hoodie-store-client/jobs/273757512#L682
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.
Writing let
has become reflexive for me 😅
Sorry about that. Can't we configure standard
for style check regarding usage of ES6 variables?
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.
unfortunately not, that is one downside of standard
. There were some discussions to do things like standard-node4
etc but I don’t think it went anywhere. So for now, we have to catch these in our CIs and reviews :D
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.
Thank god you only have to look out for ES6 specifics. Reviews are tough.
On that note, thanks for being patient with this PR, I'll fix this up today :)
I think this is good to go? Could you also update the README: add the |
I still feel |
README.md
Outdated
@@ -98,6 +98,7 @@ new Store(dbName, options) | |||
| **`options.remote`** | Object | PouchDB instance | Yes (ignores `remoteBaseUrl` from [Store.defaults](#storedefaults)) | |||
| **`options.remote`** | Promise | Resolves to either string or PouchDB instance | see above | |||
| **`options.PouchDB`** | Constructor | [PouchDB custom builds](https://pouchdb.com/custom.html) | Yes (unless preset using [Store.defaults](#storedefaults))) | |||
| **`options.validate`** | Function | Validation function to execute before DB operations | No |
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.
maybe mention that it can return a promise for async validations?
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.
Done @gr2m
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 PR is in reference of #101