How to validate alternatives in Yup? - yup

I'm used to Joi validation syntax, but right now trying to migrate some code to Yup syntax.
In Joi, I used to do this:
field: Joi.alternatives().try(
Joi.number(),
Joi.string(),
)
This makes the field either a number or a string.
How to achieve that using Yup?

UPDATE:
I created a better solution, which works for any number of whatever schema types you pass!
First you define a method called oneOfSchemas
yup.addMethod(yup.MixedSchema, "oneOfSchemas", function (schemas) {
return this.test(
"one-of-schemas",
"Not all items in ${path} match one of the allowed schemas",
(item) =>
schemas.some((schema) => schema.isValidSync(item, { strict: true }))
);
});
Then you use it as follows:
title: yup
.mixed()
.oneOfSchemas([
yup
.object()
.noUnknown()
.shape(SchemaObject1),
yup
.object()
.noUnknown()
.shape(SchemaObject2),
]),
Inspired by https://gist.github.com/cb109/8eda798a4179dc21e46922a5fbb98be6
This is the only way I found, please correct me if I'm wrong or if you have better answers:
field: yup.lazy((val) => (isNaN(val) ? yup.string() : yup.number()))
This however won't work when the alternatives are objects, which I would like to find a solution for.

Related

Mongoose $inc with enum validation failed

I'm working with featherJs and mongoose.
I have a schema with an enum which I want to $inc.
new Schema({
status: { type: Number, enum: [0, 1, 2, 3], default: 0 },
});
But when I $inc the 'status' field, it doesn't care about the enum, I can $inc as many times I want and get a status to 100000.
I don't know if it's because of featherjs or something else
Thanks
This is "working as intended", as specified in the docs:
enum: Array, creates a validator that checks if the value is strictly equal to one of the values in the given array.
So enum just creates a mongoose validator, that is:
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
In theory you should be using their update validators for this, however $inc is not supported by them, and in addition their behavior is not quite clear at times.
I personally would recommend not to use mongoose at all, it's a black box that only adds bugs and confusion. specifically when it comes to their "validators" which are not "real" validators.
So what can you do to fix this?
The easiest solution is to just do it in code, first find the object and if it fits the criteria only then do the $inc, obviously this does not give actual validation and is only supported where you'd implement it, if you have many places in the code where such update can occur this is also not optimal.
use mongodb validation, this is "real" validation that actually validates at the db level. For example you could create your collection:
db.createCollection('collection_name', {
validator: {
$jsonSchema: {
bsonType: 'object',
properties: {
status: {
bsonType: 'int',
enum: [0, 1, 2, 3],
description: 'must be a valid status integer',
},
},
},
},
validationAction: 'error',
});
Now any update with a none valid value will fail.

Ag-grid valueFormatter and Column Filter

I am having problems using ag-grid valueFormatter and column filters (https://www.ag-grid.com/javascript-data-grid/filtering/).
I have a simple colDef:
{
headerName: 'My column',
field: 'myData',
hide: true,
valueFormatter: this.formatterBooleanToHuman,
},
the formatterBooleanToHuman is a simple code to change true to Yes and false to No.
It works as expected, the issue is that we are using column filters, and when I click on the filter I have true and false to select, if I select any of them, nothing returns from the filters because the value now is actually Yes and No.
I couldn't manage to have both of them working together. To have the column filter working properly I need to remove the valueFormatter, but I would like to have both working.
I tried to apply the valueFormatter function to filterParams.valueFormatter, it did change the values on the filter but something is failing, I am getting 2 No and 1 Yes, and none of them filter.
Any suggestions?
UPDATE:
So, I found a solution, but I am not convinced it is the right way to do it.
get getcolumnDef(): Array<ColDef> {
return [
{
headerName: 'Boolean Column',
field: 'booleanValue',
hide: true,
valueFormatter: this.formatterBooleanToHuman,
filterParams: {
valueGetter: (params) => this.filterBooleanValueGetter(params, 'booleanValue')
}
}
];
}
private filterBooleanValueGetter(params: ValueGetterParams, propertyName: string) {
let isDeleted = false;
const hasValue = !!params && !!params.data && params.data[propertyName];
if (hasValue) {
isDeleted = String(params.data[propertyName]) === 'true';
}
return isDeleted ? 'Yes' : 'No';
}
So, the valueGetter works as expected and makes my filter work, I just think it is a bit "dirty" to have it to work like that, I haven't found anything on the docs saying this is the way it needs to be done. So suggestions are more than welcome.
valueFormatter applies only to data in grid. However even if the filter shows true and false instead of formatted values, it should work correctly. If filtering does not work, it may indicate some other error in your code. Maybe you depend on this in formatterBooleanToHuman method?
Anyway to format values in filter, you should define filterParams.valueFormatter like this:
{
// ...
valueFormatter: this.formatterBooleanToHuman,
filterParams: {
valueFormatter: this.formatterBooleanToHuman
}
}
For some reason, the value given to filter formatter is string instead of boolean (bug in ag-grid?), you need to adjust that.
See complete example here: https://plnkr.co/edit/o3sN3GodqQumVe09

How to filter an array of object in Mui DataGrid?

I recently changed my tables to Mui-datagrid on Material UI 5, and I have a special use case with an array of objects. I want to enable the phone number filter in this column, but the number is provided as an object list.
phone: [
{ type: "home", number: "795-946-1806" },
{ type: "mobile", number: "850-781-8104" }
]
I was expecting a 'customFilterAndSearch' or an option to customise how to search in this specific field.
customFilterAndSearch: (term, rowData) =>
!!rowData?.suppressedOptions.find(({ description }) =>
description?.toLowerCase().includes(term.toLowerCase())
),
I have made some tries with the filterOperators, but no success yet. I have made a full example here https://codesandbox.io/s/mui-data-grid-vs05fr?file=/demo.js
As far as I can see from the DataGrid documentation I don't see any way to change the filter function for a specific function.
Likely the best workaround for your use case will be converting this to a string be converting the data to a string before you pass it to the datagrid. Though you will lose the styling that you currently do by making the phone type bold.
On second though your best best would probably be to split the phone column into two columns which would probably be the cleanest way of solving your problem
Add helper function.
You could potentially add a helper function to just map all the phone lists to something like mobilePhone or homePhone
const mapPhoneObject = (rows) => {
rows.forEach((row) => {
row.phone.forEach((phone) => {
row[`${phone.type}Phone`] = phone.number;
});
});
return rows
};
I've added a fork of your snippet with my function, it is I think the most viable solution for your problem: https://codesandbox.io/s/mui-data-grid-forked-ppii8y

Rows binding in sap.ui.table.Table dynamically

I have made a title depending on variable how it's shown in: Title depending on other variable in SAPUI5
I would like to make the same with rows in sap.ui.table.Table so I tried:
rows="{= ${someData>/infos}.length > 0 ? ${someData>/infos} : ${someData>/result}}"
Whereas someData is an ODataModel (v2).
But got an error:
Uncaught TypeError: Cannot read property 'indexOf' of undefined
Problem
The problem is that you're trying to determine .length from an object. In ODataListBinding (someData>/infos), aggregations are resolved in an object rather than an array. Therefore the syntax can't work. Furthermore, the .length syntax implies that the whole collection is already available on the client-side, contradicting the purpose of sap.ui.table.Table.
Expression binding with .length makes only sense with a client-side JSONModel as mentioned here.
Alternative approach
There are multiple ways to define aggregation binding dynamically, but the most straight-forward solution would be just to access the table control reference and call bindRows dynamically. Something like this:
onInit: function() {
this.loadCountOf("SomeSet", this.bindTableRows.bind(this));
// ...
},
loadCountOf: function(entitySetName, handleCountSuccess) {
const odataModel = /*...*/;
odataModel.read(`/${entitySetName}/$count`, {
success: count => handleCountSuccess.call(this, +count),
});
},
bindTableRows: function(count) {
this.byId("myTable").bindRows({
path: count > 0 ? "/SomeSet" : "/TheOtherSet",
// ...
});
},
API reference: sap.ui.table.Table#bindRows
the errors seem to tell you that either infos or result is undefined. You should check the current value of those arrays.
Anyway, it's not a really good idea to bind table rows like that IMHO.
What's you scenario?

Pass Dynamic Variable to highlightResults in Autocomplete.js

I'm building a typeahead component for my Vue application that searches Algolia, which has several different indexes to search in different places, so I've created props to be passed in to set the input placeholder, search index, and displayKey.
All works well except my highlighting function for suggestions.
I'm sure this is something simple but I can't get the highlight return to pick up the dynamic prop passed in.
$('.typeahead').autocomplete({ hint: false }, [{
source: $.fn.autocomplete.sources.hits(this.client, { hitsPerPage: 5 }),
displayKey: this.display,
templates: {
suggestion: (suggestion) => {
return suggestion._highlightResult.{this.display goes here}.value;
}
}
}]).on('autocomplete:selected', (event, suggestion, dataset) => {
console.log(suggestion, dataset);
})
If I omit the highlighting all works perfectly.
I knew it was simple, call it via array key instead of dot notation.
return suggestion._highlightResult[this.display].value;