-
Notifications
You must be signed in to change notification settings - Fork 43
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
Basic implementation of validator-cvapi #48
base: main
Are you sure you want to change the base?
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/pberganza10/felte-docs/Bm6MEgUUUD2xcQxWpT1TbERTyyjF |
@pablo-abc I just realized that I had to choose "draft pull request" beforehand. As you may notice, I'm not that familiar with github. Should I close this and reopen as draft? |
Codecov Report
@@ Coverage Diff @@
## main #48 +/- ##
==========================================
- Coverage 99.32% 98.68% -0.64%
==========================================
Files 38 39 +1
Lines 885 915 +30
Branches 282 292 +10
==========================================
+ Hits 879 903 +24
- Misses 5 9 +4
- Partials 1 3 +2
Continue to review full report at Codecov.
|
Just transformed it to a draft! No worries about it. Give me a while to look over the rest! |
Going further into why I'm proposing an API that uses the validity states as key rather than the fields:
{
account: {
profile: {
name: '',
lastName: '',
},
preferences: {
publicEmail: false,
},
}
}
More or less what I had in mind was to either accept a string or a function, which would give quite a bit of flexibility while keeping the code the developer has to write quite concise. Something like this: {
required: 'This field is required',
tooShort: ({ value, rule }) => `${value} is needs to be at least ${rule} characters long. It's currently ${value.length} characters.`,
} (Of course the properties passed to it and naming are up for debate) This API would potentially allow for per-field messages in edge cases. {
required: ({ name }) => {
if (name === 'password') return 'You probably want a password for your account';
return 'This field is required';
},
} Needing per-field messages as the main option seems to favor edge-cases rather than most common cases of validation reporting. And of course we'd assume that if no value is provided, the default browser's message would be used. |
So we are going by levels of possible edge cases.
I hope I'm making sense right now, to be honest 😅😅 |
Regarding testing: Besides Of course, I wouldn't worry about this right now. And you're doing a tremendous amount of work already (I can't thank you enough!). When a proper an API has been established I'd be happy to do the testing. Btw, I've added you as a contributor in the README. Following the |
I'll think I'll have to think about the best solution a little bit more, but just some quick thoughts:
And some more general ones:
|
I understand your concern here. But as far as I can see this can still be considered an edge case. Even when context is needed, most of the times it has more to do with what the input contains rather than which input it is. While the default behaviour (browser messages) would remain the same; for simple validation requirements with generic validation messages, with a per-field API we are asking the developer to go with something like this: {
field1: 'This field is required',
field2: 'This field is required',
field3: 'This field is required',
field4: 'This field is required',
// ...
} And this can start to look even more repetitive with more validation added: {
field1: (state) => {
if (state.tooShort) return 'The value you typed is too short';
if (state.valueMissing) return 'This field is required';
return 'Invalid value';
},
field2: (state) => {
if (state.tooShort) return 'The value you typed is too short';
if (state.valueMissing) return 'This field is required';
return 'Invalid value';
},
field3: (state) => {
if (state.tooShort) return 'The value you typed is too short';
if (state.valueMissing) return 'This field is required';
return 'Invalid value';
},
field4: (state) => {
if (state.tooShort) return 'The value you typed is too short';
if (state.valueMissing) return 'This field is required';
return 'Invalid value';
},
// ...
} A per-field API would require generic validation messages to be repeated for each field that uses it in the form. |
The reason the validation messages functionality is not higher up is that the This is why a In a more general sense, an For your second point I do agree that the Anyway I'm definitely up for discussing this more. Ultimately, improving Felte into providing the best DX/UX is the goal and any changes that need to be done to achieve that are never out of the questions (which is why I have kept Felte's version pre 1.0.0 until more feedback is received). |
Nesting/General responsibilities: So I would opt for either a) don't support nested states/configs in this package (my preferable option) or b) lift up the infrastructure and provide clear interfaces which should be implemented by every validator. Config/error-messages export type ValidatorConfig = {
defaults: (state: ValidityState, control: FormControl) => string,
[path: string]: (state: ValidityState, control: FormControl) => string
}; And this simple implementation: // check if there is an error-message for that control in the supplied config
// either for the specific path or as a default
if (options?.[path] || options?.defaults) {
cvErrors[path] = (options?.[path] || options?.defaults)(control.validity, control);
// if no supplied error-msg can be found, fall back to the browser-supplied default
} else {
cvErrors[path] = control.validationMessage;
} The reasoning behind that interface and implementation follows three thoughts:
Testing |
I'd argue with your first point: Felte does support nesting in its core. Using named Currently in our The function argument you pass seems perfect to me! In fact, with that signature alone ( From what I've established previously, if we want to adhere to the contract our other core packages have with Felte, having a In general I still don't see the benefit of having a per-field configuration (+ the complexity of implementing it) when a function argument provides the flexibility for that as an edge case. (either if we do a single function argument as proposed in your message or if we use a configuration based on validityStates). As a side note, I apologise for not answering earlier. This week has been quite hard on balancing OSS + work time 😅. |
The question here is basically how "support" is defined. But you are right in the sense that What do you think?
\o/
This time, I'm a bit in a hurry. So no apologies necessary. Everything's fine! |
Here is the basic implementation. At the moment, I have two main areas which need some more work:
Atm, it supports defaults per ValidityState-error-key and my "per-control-api".
It resolves the messages with priority for the "per-control-api" as I consider them to be more specific.
While implementing the "defaults-api", I realized that it's not guaranteed, that only one error-key is true. So I do a simple find on them and take the first match. I had no clue how to prioritize them further, as I couldn't find any information besides the shape of the state.
So the interface is quite flexible, but it needs some knowledge about the resolution-strategy and may be confusing to new users.
To be honest, I don't like this implementation very much as it forces you implicitly to supply every possible value beforehand or may leak default-messages where this may not the desired outcome. Afaik it is not guaranteed which error-keys in ValidityState are flipped under which circumstances. At the end it may be browser-specific.
That's the reason why I opted for a clearer resolution-strategy in the first place where a returned string/result will definitly be the error-message of the invalid control.
It would be perfectly fine for me if you decide the direction how to go forward with this one.
Example of a perfectly possible config for the actual implementation:
I'm quite new to svelte and I'm a bit unsure how to test this one as this is a library on one hand where I would normally test its surface and dependent on the DOM on the other, where I would test the DOM normally. This gets more confusing to me as in svelte both domains are more intermixed as I'm used to in the react-world. Do you have any advice on this?