check if an input variable is string or array using joi - joi

I have an api that in the past developments would receive comma separated strings as valid input and used the following as the validator:
Joi.string()
But now I want to implement the same variable using array of strings as mentioned here https://github.com/glennjones/hapi-swagger/issues/119. So the new check would be:
Joi.array().items(Joi.string())
But I do not want to break the backward compatibility of the code. Is there a way to check both conditions for the variable?
I am a newbie to Joi, so any help or guidance would be appreciated. Thanks in advance.

Take a look at .alternatives().try() which supports multiple schemas for a single field.
For example:
Joi.alternatives().try(Joi.array().items(Joi.string()), Joi.string())
This will validate both arrays of strings and plain strings, however as I'm sure you know, you'll still need server side logic to check which format the value is so you can process it properly.

You can use alternatives.try or shorthand [schema1, schema2]
const Joi = require('joi');
const schema1 = {
param: Joi.alternatives().try(Joi.array().items(Joi.string()), Joi.string())
};
const result1 = Joi.validate({param: 'str1,str2'}, schema1);
console.log(result1.error); // null
const result2 = Joi.validate({param: ['str1', 'str2']}, schema1);
console.log(result2.error); // null
const schema2 = {
param: [Joi.array().items(Joi.string()), Joi.string()]
};
const result3 = Joi.validate({param: 'str1,str2'}, schema2);
console.log(result3.error); // null
const result4 = Joi.validate({param: ['str1', 'str2']}, schema2);
console.log(result4.error); // null

Related

Joi validation with dollar sign in the number text

Is it possible to extend joi to allow for a '$' in the number() validation?
My input is a string like "$12.34". When I attempt to validate this using Joi.number() I receive an error "{Field} must be a number". All I need is to remove the $ and it works fine. Is there any way to do this in the schema definition so that I don't have to scrub my incoming data before calling validate?
const results = Joi.number().validate("$12.34") // fails
const results = Joi.number().validate("12.34") // succeeds
If you are okay with regex then you can use regex to validate the string as follows:
Joi.string().regex(/\$\d+(\.?\d+)?/)
// \$ check for $
// \d+ digit 1 or more
// \.? . one or zero
// (\.?\d+)? match group for 0 or 1 time
As input type is string you can be sure that it contains $ by two ways i.e.
replace $ with empty string.
Joi.number().validate("$12.34".replace("$",""))
splitting string at $ and then checking 2nd part (less secure)
Joi.number().validate("$12.34".split("$")[1])
Last option is to strip of $ every-time and then pass remaining part to check if it's number.
The short answer is to extend number with a custom prepare method.
After trying everything I could think of I looked at the source on github and found this test
it('extends number to support comma delimiter', () => {
const custom = Joi.extend({
type: 'number',
base: Joi.number(),
prepare(value, helpers) {
if (typeof value !== 'string') {
return;
}
return { value: value.replace(',', '.') };
}
});
expect(custom.number().validate(2.0)).to.equal({ value: 2.0 });
expect(custom.number().validate('2.0')).to.equal({ value: 2.0 });
expect(custom.number().validate('2,0')).to.equal({ value: 2.0 });
expect(custom.number().validate('2,0', { convert: false }).error).to.be.an.error('"value" must be a number');
expect(custom.number().validate(undefined).error).to.not.exist();
});
which is basically what I am trying to do so I modified it to fix up '$' instead of ',' and voila
results1 will pass and results2 will error out.

String transformation for subject course code for Dart/Flutter

For interaction with an API, I need to pass the course code in <string><space><number> format. For example, MCTE 2333, CCUB 3621, BTE 1021.
Yes, the text part can be 3 or 4 letters.
Most users enter the code without the space, eg: MCTE2333. But that causes error to the API. So how can I add a space between string and numbers so that it follows the correct format.
You can achieve the desired behaviour by using regular expressions:
void main() {
String a = "MCTE2333";
String aStr = a.replaceAll(RegExp(r'[^0-9]'), ''); //extract the number
String bStr = a.replaceAll(RegExp(r'[^A-Za-z]'), ''); //extract the character
print("$bStr $aStr"); //MCTE 2333
}
Note: This will produce the same result, regardless of how many whitespaces your user enters between the characters and numbers.
Try this.You have to give two texfields. One is for name i.e; MCTE and one is for numbers i.e; 1021. (for this textfield you have to change keyboard type only number).
After that you can join those string with space between them and send to your DB.
It's just like hack but it will work.
Scrolling down the course codes list, I noticed some unusual formatting.
Example: TQB 1001E, TQB 1001E etc. (With extra letter at the end)
So, this special format doesn't work with #Jahidul Islam's answer. However, inspired by his answer, I manage to come up with this logic:
var code = "TQB2001M";
var i = course.indexOf(RegExp(r'[^A-Za-z]')); // get the index
var j = course.substring(0, i); // extract the first half
var k = course.substring(i).trim(); // extract the others
var formatted = '$j $k'.toUpperCase(); // combine & capitalize
print(formatted); // TQB 1011M
Works with other formats too. Check out the DartPad here.
Here is the entire logic you need (also works for multiple whitespaces!):
void main() {
String courseCode= "MMM 111";
String parsedCourseCode = "";
if (courseCode.contains(" ")) {
final ensureSingleWhitespace = RegExp(r"(?! )\s+| \s+");
parsedCourseCode = courseCode.split(ensureSingleWhitespace).join(" ");
} else {
final r1 = RegExp(r'[0-9]', caseSensitive: false);
final r2 = RegExp(r'[a-z]', caseSensitive: false);
final letters = courseCode.split(r1);
final numbers = courseCode.split(r2);
parsedCourseCode = "${letters[0].trim()} ${numbers.last}";
}
print(parsedCourseCode);
}
Play around with the input value (courseCode) to test it - also use dart pad if you want. You just have to add this logic to your input value, before submitting / handling the input form of your user :)

How to check elements are rendered with a specific sorting with react-testing-library?

With its user-centric approach, my understanding of RTL is that testing sorting of elements should look something like.
const foo = queryByText('foo');
const bar = queryByText('bar');
expect(foo).toBeInTheDocument();
expect(bar).toBeInTheDocument();
expect(foo).toShowBefore(bar); // THIS IS MY PSEUDOCODE
However, I couldn't find a .toShowBefore() or equivalent function in RTL.
What's the correct way to test that content is displayed in a certain order?
I don't believe React Testing Library has an idiomatic way to do this. But if you can parse your DOM, then you should be able to get the textContent of various Elements and assert on that.
const select = screen.getByTestId('select');
const children = select.children;
expect(children.item(0)?.textContent).toEqual('Interface A');
expect(children.item(1)?.textContent).toEqual('Interface B');
expect(children.item(2)?.textContent).toEqual('Interface C');
You can use compareDocumentPosition.
For example
const e1 = getByText('a');
const e2 = getByText('b');
expect(e1.compareDocumentPosition(e2)).toBe(2);
Here getByText is returned from the render function.
You could give each row a test id:
<tr data-testid='row'>...</tr>
and then expect the rows to be in order:
const rows = screen.getAllByTestId('row')
expect(within(rows[0]).queryByText('A')).toBeInTheDocument()
expect(within(rows[1]).queryByText('B')).toBeInTheDocument()
expect(within(rows[2]).queryByText('C')).toBeInTheDocument()

How to get a range from start & end index with Office.js while developing MS Word Add-in

I want to get a sub range by character index from the parent paragraph. What's the suggest way to do so? The only method I found to shrink the range is "Paragraph.search()"
ref:
Word.Range: https://learn.microsoft.com/en-us/javascript/api/word/word.range?view=office-js
Word.Paragraph: https://learn.microsoft.com/en-us/javascript/api/word/word.paragraph?view=office-js
My use case:
I'm writing a markdown plugin for MS Word and I'm trying to parse the following paragraph.
A **bold** word
The output from markdown parser is {style:"strong",start:2,end:9}. So I want to apply bold style to the targeting range.
Found a way just now. The key is passing an empty separator to Paragraph.getTextRanges([""]) I'm not sure how bad the performance would be.
const makeBold = async (paragraph:Word.Paragraph,start:number,end:number) => {
const charRanges = paragraph.getTextRanges([""])
charRanges.load()
await charRanges.context.sync()
const targetRange = charRanges.items[start].expandTo(charRanges.items[end])
targetRange.load()
await targetRange.context.sync()
targetRange.font.bold = true
await targetRange.context.sync()
}
The existing answer didn't work for me (it seems like getTextRanges() no longer accepts empty strings).
So I have adapted the answer to use paragraph.search() instead.
async function getRangeByIndex(paragraph: Word.Paragraph, start: number, end: number) {
const charRanges = paragraph.search('?', { matchWildcards: true })
charRanges.load()
await charRanges.context.sync()
const targetRange = charRanges.items[start].expandTo(charRanges.items[end])
targetRange.load()
await targetRange.context.sync()
return targetRange
}

how to parse SMValue to String array

I am just getting familiar with the StackMob server side custom code sdk and i am trying to get a relationship field and iterate through it in the form of a String array. How do i do that ? is it possble to iterate through it without parsing it into an array?
DataService ds = serviceProvider.getDataService();
List<SMCondition> query = new ArrayList<SMCondition>();
query.add(new SMEquals("product_id", new SMString(jsonObj.getString("product_id"))));
List<SMObject> results = ds.readObjects("product", query);
SMObject product= results.get(0);
//product.getValue().get("categories"); how do i get this to be a String array?
At its simplest, that would look something like this:
List<SMValue> categories = (List<SMValue>)(rawObj.getValue().get("categories").getValue());
for (SMValue smString : categories) {
SMString stringValue = (SMString)smString.getValue();
//do whatever you want with the string value here
}
Obviously there are some unchecked casts in here, so you will want to add type/null checking to the appropriate sections depending on your data & schema.