joiful-ts / joiful Goto Github PK
View Code? Open in Web Editor NEWTypeScript Declarative Validation for Joi
TypeScript Declarative Validation for Joi
I expect request body to be an array.
How can I achieve something like this:
class ArrayItem {
@StringSchema()
something: string;
...
}
// Array that can be validated:
const ArrayClass: Array<Arrayitem>= CreateArraySchema( ArrayItem );
Decorator for custom error definition is not exposed (any.error).
I tried to expose it:
export function CustomError(err: Error | ValidationErrorFunction, options?: ErrorOptions) : PropertyDecorator {
return constraintDecorator([], (schema : Schema) => {
return schema.error(err, options);
});
}
but it fails.
Error 1) I am receiveing Joi error instead of my custom error.
Error 2) If it is not first decorator of property then I have runtime error ConstraintDefinitionError: A validation schema already exists for property:
Any ideas how to fix it (add custom error decorator)?
I have a model similar to below:
export class Address {
public currentAddress: string;
public permanentAddress: string;
}
The requirement here is that at-least currentAddress
or permanentAddress
should be provided. And @or decorator fits the bill perfectly here, however i'm unable to get it working.
Looking at some of the joi examples, it seems .or
is used on the schema of the object (i.e. Address
class in my example above) rather than on the keys (i.e. currentAddress
, permanentAddress
etc).
Can someone provide an example of how @or decorator be used? I could not find anything in the tests too.
Thanks in advance.
In later versions of @types/joi
WhenOptions
is no longer generic. So if the consuming application is using the latest version of joi
then it will have the following build error:
node_modules/tsdv-joi/dist/constraints/any.d.ts(85,56): error TS2315: Type 'WhenOptions' is not generic.
I see a few possible solutions:
joi
and @types/joi
as peer-dependenciesjoi
and @types/joi
This code can fail:
import Optional = AnyConstraints.Optional;
import { AnyConstraints } from 'tsdv-joi';
This code is fine:
import { AnyConstraints } from 'tsdv-joi';
const Optional = AnyConstraints.Optional;
This can occur when the order of imports changes, which can be caused by "Organise imports" operations in IDEs like IntelliJ IDEA.
A possible fix is to use objects instead of namespaces to contain each type's constraints. That will force the use of the latter syntax.
JavaScript files are generated into a dist
directory. The NPM module is packaged to include the whole dist
directory.
This makes importing specific items from sub-modules awkward, since you need to write import ... from 'tsdv-joi/dist/constraints/any'
.
We copy package.json, README.md, and any other files to be published, into dist
(or some other publishing directory alongside the generated JS files), and publish from that directory instead of the project root.
Currently, there is runtime validation against the generated property metadata, e.g. to restrict string constraints to string properties. I think we can do better, and elevate this to compilation errors.
This snippet from Stack Overflow could help:
https://stackoverflow.com/a/47425850
I have an object where I need one property to be a generic key/value object.
So in TS:
class MyEntitiy{
options: {[key: string]: OptionType};
}
Usually with joi I would do joi.object().unknown(true)
or joi.object().pattern(/.*/, joi.object())
. Is there a way for this in joiful?
I also could imagine other cases where more generic joi functions are useful e.g.
class MyEntity{
property: string|SomeObjectType;
}
So something generic like @joiful.schema(mySchema)
would be useful. For this specific example it would be something like @joiful.schema(joiful.joi.alternatives(joi.string(),joi.object()))
.
Hi. I use your lib, thank you!
I'm having some difficulties
I use GlobalPipes (NEST.JS) which checks all entry data
app.useGlobalPipes(new JoiValidationPipe());
But sometimes i don't need to use DTO
I had to write the following code to not get mistakes in JoiValidationPipe
Can we have method
jf.isJoiSchema(value, metatype)
for checking before validation ?
All dependencies should be added using exact versions. We can enforce this via a .yarnrc
file.
Not sure why.
Might just need to add:
.npmignore
...
!**/*.js
!**/*.d.ts
@string().allow() pass unallowed strings.
Env:
Compiler options:
{
"compilerOptions": {
"esModuleInterop": true,
"noImplicitAny": true,
"module": "CommonJS",
"moduleResolution": "node",
"allowJs": true,
"strict": true,
"isolatedModules": true,
"pretty": true,
"sourceMap": true,
"target": "es2018",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"rootDir": "./",
"outDir": "./dist"
},
"exclude": ["dist", "node_modules"]
}
Reproduce:
import * as jf from 'joiful';
class C {
@jf.string().allow('a', 'b').required()
ab!: string;
}
const c = new C();
c.ab= 'x';
// expected error != null
console.dir(jf.validateAsClass(c, C));
// {
// error: null,
// value: C { ab: 'x' },
// then: [Function: then],
// catch: [Function: catch]
//}
Is it possible to provide function as custom validator like below?:
function myValidationFunc(value, object, property): void | string {
if ( isInvalid(value) ) {
return 'Reason of validation error';
}
// no error
}
class C {
@jf.custom(myValidationFunc)
val: string
}
If no, then how to achieve it? (I know only how to create custom validator basing on predefined joiful
decorators)
Release notes and breaking changes: hapijs/joi#2037
E.g. consider a tree node which has children, each child can be a tree node. Passing the class to "@NestedArray()", or calling "@ArrayConstraints.Items(getJoiSchema(MySchemaClass))", does not seem to work. I think this is because the schema has not been fully defined by the time it's passed to the decorator, but these decorators force the resolution of the (incomplete) Joi schema.
e.g.
class TreeNode {
@Required()
tagName: string;
@NestedArray(TreeNode)
children: TreeNode[];
}
The "children" property will get stripped out.
This can be resolved by adding support for Joi's "lazy()" function, which allows passing in a function that returns a schema. (In our case, we could make it also accept a schema class.)
All decorators should have jsdoc comments, describing how to use each decorator. This information should be duplicated from the Joi documentation, ideally with examples updated to use decorators*.
The rules in TypeStrict look good for catching buggy code.
I was poking around the any constraints and noticed that Concat
looks like it's possibly implemented incorrectly. As you can see schema
is declared twice.
/**
* Returns a new type that is the result of adding the rules of one type to another.
*/
export function Concat(schema : Schema) : PropertyDecorator {
return constraintDecorator([], (schema : Schema) => {
return schema.concat(schema);
});
}
I suspect this should be:
/**
* Returns a new type that is the result of adding the rules of one type to another.
*/
export function Concat(schema : Schema) : PropertyDecorator {
return constraintDecorator([], (existingSchema : Schema) => {
return existingSchema.concat(schema);
});
}
Empty seems to be the same:
/**
* Outputs the original untouched value instead of the casted value.
*/
export function Empty(schema : any) : PropertyDecorator {
return constraintDecorator([], (schema : Schema) => {
return schema.empty(schema);
});
}
@laurence-myers if you agree I'm happy to create a PR with the fix.
Looks like a copy/paste error.
Interestingly I noticed there are no keywords defined in package.json
. I'm not sure if I accidentally removed them as part of the major update or if they were never there (can't be bothered checking hehe) but either way I think we should add them.
The keywords defined in the github repo would probably be a good start. I'd also add "tsdv-joi" as a keyword.
All constraint decorators should have unit tests.
tsdv-joi requires an old version of Joi and its type definitions, let's update to the latest.
This will introduce breaking changes. For example, Joi removed all "date" functionality from the core library, to remove the dependency on moment, thereby minimising the library footprint and making it easier to package for the browser. For tsdv-joi, this means we'd need a separate project to provide decorators for joi-date.
Given a property with type "number | null", the inferred type is "Object", not "Number". This breaks the automatic schema detection.
There needs to be a way to support this, or bypass the automatically chosen schema.
Another enhancement request: I was wondering how you'd feel about replacing Mocha with Jest?
Chai's API can be a little inconsistent with whether you should use brackets or not after assertions. Jest's API is straight-forward, no having to guess the correct combination of nested property names, and every assertion is a function, not a property getter, so you always use brackets. e.g. expect(thing).toBeTruthy()
.
Support for spying on mock functions comes out of box. No need to use a separate library like Sinon. Naive example:
describe('AdvancedMath', () => {
it('should square numbers', () => {
const basicMath: Partial<BasicMath> = {
multiply: jest.fn().mockReturnValue(3);
};
const advancedMath = new AdvancedMath(basicMath);
const result = advancedMath.square(5);
expect(result).toBe(25);
expect(basicMath.multiply).toHaveBeenCalledOnce();
expect(basicMath.multiply).toHaveBeenCalledWith(5, 5);
});
});
Support for mocking modules out of the box. No need to use a separate library like mockery. Unlike mockery you don't need to ensure your mock is registered before the library is required in the code that you're testing.
Comes with Instanbul support built-in, allowing you to easily track code-coverage and even fail when minimum thresholds for functions, statements, branches and lines are not met.
One test library to rule them all! 💍
Indirectly related: Also wanted to ask how you'd feel about putting the tests in the same directory as the files being tested.
This has the following advantages:
Makes it clear that foo.ts
is not tested when it doesn't have an accompanying foo.test.ts
file right next to it.
No need to maintain a mirrored directory structure of your tests.
Might be worth adding an API Reference page that goes into more detail than the README.
This regression seems to have been introduced in build 7, which removed the use of npm pre/post-publish scripts.
Hi there!
Probably I haven't found the possibility to transform a Json into Class after verify if the Json is a valid one.
Just in case this feature is not included ¿Are you guys planning to add it?
Joi was originally required as a peer dependency, for easier integration with existing codebases that already used Joi. I don't think that provides much value now. Let's just package Joi (and @types/joi) as a regular dependency, and export Joi from tsdv-joi, so that consuming apps don't also need to have their own dependency on Joi (and @types/joi) for constructing schemas for e.g. the @Items()
decorator.
Relates to #21.
I'm trying to validate an array of strings (i.e. ["abc", "def", "ghi"] ) using something like below:
@NestedArray(String)
public myArr: Array<string>;
But validation fails with error that an object is expected.
I looked at @array decorator but it doesn't accept 'type'.
@Items accepts type but it needs a schema. I could do something like this:
@Items(Joi.string())
But i'm trying to avoid exposing Joi in my code and work with tsdv-joi as much as possible.
Is there any other way to achieve this?
yarn add joiful
/ npm add joiful
)class Foo {
@jf.string()
bar: string;
}
const customJoi = Joi;
const validator = new Validator({
joi: customJoi,
});
const instance = new Foo();
expect(instance).toBeValid({ validator });
Fails with error:
Error: "joi" is not allowed
at Object.<anonymous>.internals.Object.checkOptions (*snip*\joiful\node_modules\@hapi\joi\lib\types\any\index.js:105:19)
at Object.<anonymous>.internals.Object._validateWithOptions (*snip*\joiful\node_modules\@hapi\joi\lib\types\any\index.js:758:18)
at Object.<anonymous>.module.exports.internals.Any.root.validate (*snip*\joiful\node_modules\@hapi\joi\lib\index.js:145:23)
at Validator.validateAsClass (*snip*\joiful\src\validation.ts:1892:38)
at Validator.validate (*snip*\joiful\src\validation.ts:1832:19)
This is because the Joiful validator's options are passed to the Joi validate()
method, which validates its options and rejects our additional args.
Had a few questions about how to validate arrays. Would be good to add an example or two to the README.
Currently the instance of joi
used by tsdv-joi
can be overridden by the application developer. However this is currently a global, which means the application could be using a library that also uses tsdv-joi
and expects different behaviour from the joi
instance or sets the instance itself.
In order to work better with other libraries, as @laurence-myers suggested, it would be better if consumers could create their own instances of tsdv-joi
where they could set any specific configurations they need in complete isolation. As well as setting the joi
instance, this would also be useful for registering a label provider function in isolation.
Existing exported decorators would continue to work as is with a default instance of tsdv-joi
.
For example:
import { TsdvJoi } from 'tsdv-joi';
import * as joi from 'joi';
import * as Case from 'case';
const tsdvJoi = new TsdvJoi({
joi,
labelProvider: propertyName => Case.sentence(propertyName)
});
CircleCI is only running 23 tests, instead of 90+ tests. Looks like it's not picking up any tests contained in the "constraints" sub-directory. For some reason this is working locally.
A logo just adds a little bit of extra polish.
I see this example in #38
class Bar {
@StringSchema()
public name!: string;
}
class Foo {
@NestedArray(Bar)
public myArr: Array<Bar>;
}
Is there a equivalent of NestedArray
in new fluent api?
I can see @jf.array().items(type: Joi.Schema)
but I'm not sure how to get the joi schema of Bar
.
class FooClass {
bar: number;
}
class BazSchema {
@ObjectConstraints.ObjectSchema()
qux: FooClass;
}
This fails because ObjectSchema()
only accepts properties of type Object
, but the metadata value of class instances have a type of Function
, because in JavaScript classes are syntactic sugar over functions.
A quick fix is to modify the decorator to allow "Function" as a valid type.
Is it possible to use the Joi "when" operator? In Joi, one can do:
Joi .string() .valid.apply(this, days) .required() .when("action", { is: "DELETE", then: Joi.string().optional(), otherwise: Joi.string().required() })
However, with jf,
@(jf .string() .valid.apply(this, days) .required() .when("action", { is: "DELETE", then: jf.string().optional(), otherwise: jf.string().required() }))
I receive an error:
TypeError: jf.string(...).valid.apply(...).required(...).when is not a function
https://circleci.com/gh/laurence-myers/tsdv-joi/33
Husky expects Node v8, but tsdv-joi and its CircleCI job expects to run on Node v6. Husky then complains about the version mismatch.
If the tests had been run on the PR, this would have been caught earlier. I have discovered that CircleCI was not running CI for PRs from forks. I've now turned on that setting.
#!/bin/bash -eo pipefail
yarn install
yarn install v1.15.2
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
error [email protected]: The engine "node" is incompatible with this module. Expected version ">=8". Got "6.17.1"
error Found incompatible module
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
Exited with code 1
I recommend using a couple libraries to help manage versioning and change logs better.
commit-msg
git hook and trigger validation using commitlint. Husky is a single library you can use to handle precommit events too.git commit
(e.g. npm run commit
) that prompts your for the type, scope, message and optional body and footer for your commit. Not essential if you are familiar with the commit message convention but I like it.CHANGELOG.md
feat
will increment the minor version, otherwise only the patch is incremented.When you're ready to ship your release you can push your changes to github (including tags) and publish to npm.
I was put onto this technique by someone at my previous job. It seems to be used in a few big libraries. Anyway, I have since adopted it in a few libs as I think it makes a lot of sense and I'm happy to report has been working well. I'd be happy to create a PR if you're at all interested.
https://github.com/hapijs/joi/blob/v10.6.0/API.md#binary---inherits-from-any
There's currently no decorators implemented for the binary schemas.
Adding these may have implications for projects using tsdv-joi in the browser, but since they'd have to get joi working in the browser anyway, it's hopefully okay.
Feature question:
Hi, I wonder if it is technically possible to allow placing sub-properties validators inside class instead of defining new class:
class Config {
@jf.object().required()
db: {
@jf.object().required()
credentials: {
@jf.string().required()
type: string,
@jf.object().optional()
userAndPass?: {
@jf.string().required()
user: string,
}
}
}
}
??
To assist existing consumers of tsdv-joi
, we should add a migration guide, probably as a wiki page.
The migration guide should cover breaking changes and how to upgrade consuming code to use the new API.
I've been using the library a fair bit lately and I've started thinking it would be nice to support a fluent API.
So instead of this:
export class FormFields {
@Required()
@Min(1)
@Label('First Name')
@StringSchema()
firstName: string;
@Required()
@Min(1)
@Label('Last Name')
@StringSchema()
lastName: string;
@Required()
@Label('E-mail Address')
@Email()
@StringSchema()
emailAddress: string;
@Required()
@Label('Age')
@Min(18)
@NumberSchema()
age: number;
}
You could use:
export class FormFields {
@StringSchema().label('First Name').min(1).required()
firstName: string;
@StringSchema().label('lastName').min(1).required()
lastName: string;
@StringSchema().label('E-mail Address').email().required()
emailAddress: string;
@NumberSchema().label('Age').min(18).required()
age: number;
}
Admittedly, at first it looks not too dissimilar from simply removing the line-breaks from the individual decorators, but it does have a couple advantages.
When simply typing in a constraint like Min
and allowing the IDE/plugin to automatically add the import statement for you, you have to be careful that it imports from the correct constraint directory (string, number, date, array, object). By using a fluent API you'd always get the right min function.
A fluent would also limit the validation constraints you have available for your chosen schema type, this way you can't accidentally include constraint decorators that make no sense for your type, which can easily happen when copying and pasting.
It also makes the API a little more discoverable. You don't have to hunt through the .d.ts
files of tsdv-joi
to find the right name of the decorator you need. You just start with the type e.g. @StringSchema()
hit dot and you immediately see the constraints you have available for that type.
I've also noticed some constraints error at runtime if the schema type is not immediately declared before (i.e. below) the constraint in question. I don't remember which ones did this now but I try to remember to always declare the schema type immediately above the property. This is not obvious to a new user though and an easy mistake to make. A fluent API would avoid this.
Anyway, was just a thought. There are probably other ways to address this issues too which may require less work.
Make creating user friendly property labels easier (i.e. less code) for developers.
Joi's error messages are mostly user friendly except for the fact that they are based on property keys. A consumer can specify user friendly labels for properties via the Label
decorator which is good. However if most of the time the label is simply the property key as title or sentence case, specifying a label decorator on every property feels like unnecessary boilerplate and doesn't make for a great developer experience.
If we had the ability to register a label provider function once at app startup, developers would be spared from having to specify labels for every field.
Something like this:
import { registerLabelProvider } from 'tsdv-joi';
import * as Case from 'case';
registerLabelProvider(propertyName => Case.sentence(propertyName));
tsdv-joi
is not memorable, nor does it roll off the tongue. Let's rename the library, and go with the obvious option (seems it's already taken, let's find a new name).joi-decorators
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.