Coder Social home page Coder Social logo

joiful's People

Contributors

bl4ckb0ne avatar brvenkat avatar codeandcats avatar dependabot-preview[bot] avatar dependabot[bot] avatar laurence-myers avatar probil avatar ujwal-setlur avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

joiful's Issues

How to Validate Array class?

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 );

any.error constraint

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)?

Unable to use @Or 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.

WhenOptions is not generic

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:

  • (a) Depend on the specific versions of joi and @types/joi as peer-dependencies
  • (b) Update to the latest version of joi and @types/joi
  • (c) Both of the above?

Importing constraints contained within namespaces can cause issues

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.

Directly importing sub-modules requires "dist" in the path

Problem

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'.

Proposed solution

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.

Unknown/Pattern object

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())).

Api method "isJoiSchema" before validation

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
image

I had to write the following code to not get mistakes in JoiValidationPipe

image

Can we have method
jf.isJoiSchema(value, metatype)
for checking before validation ?

@string().allow not working

@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]
//}

Custom validation function

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)

Lazy() support required for recursive schemas

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.)

hapijs/joi#379

Add Joi documentation as jsdoc comments to constraint decorators

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*.

  • Examples may not be necessary if we have unit tests demonstrating how to use each decorator: see #23.

Concat decorator implementation appears wrong

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.

Add keywords to package.json

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.

Update to Joi v15+ (the latest)

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.

Use Jest as test framework

Another enhancement request: I was wondering how you'd feel about replacing Mocha with Jest?

Motivation

  • 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.

Add API Reference page

Might be worth adding an API Reference page that goes into more detail than the README.

Feature to transform Json to Class

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?

Make Joi a regular dependency (not a peer dependency)

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.

Unable to validate an array of strings

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?

Improve README

  • Add obligatory install instructions (e.g. yarn add joiful / npm add joiful)
  • Improve code examples
    • Include imports
    • Make examples relatable (e.g. Todo, Login form, etc)
    • Show to create custom short-hand decorators using new api syntax
  • Meet minimum flare quota by adding more badges
    • version and link to npm
    • test coverage
    Screen Shot 2019-08-25 at 8 04 46 pm

Custom `joi` in Validator options causes error

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.

Creating isolated instances of tsdv-joi

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)
});

Validate arrays of objects using new fluent api

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.

ObjectSchema() cannot be applied to properties with a class (instance) type

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.

"when" operator?

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

CircleCI is not executing tests, due to husky requiring Node.js v8

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.

Options

  • Remove husky. (It's only used for development, and there might be some other way to achieve what it's doing.)
  • Update tsdv-joi (and the CircleCI docker container) to Node.js v8. It seems v6 reached EOL on 2019-04-30.

CircleCI log

#!/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

Conventional commits

I recommend using a couple libraries to help manage versioning and change logs better.

  • commitlint. Used to ensure commit messages conform to a standard convention
  • Something like husky to handle the commit-msg git hook and trigger validation using commitlint. Husky is a single library you can use to handle precommit events too.
  • commitlint/prompt-cli - CLI to use in place of 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.
  • standard-version. Used when you're ready to create a release. It will:
    1. Parse the previous commit messages
    2. Add your messages to CHANGELOG.md
    3. Bump the package version by either a patch, minor or major version depending on your commit messages. i.e. a commit including the words "BREAKING CHANGES" will increment the major version, otherwise a commit type of feat will increment the minor version, otherwise only the patch is incremented.
    4. Commit.
    5. Add a release tag.

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.

Nested validation improvement

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,
      }
    }
  }
}

??

Create Migration Guide

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.

Fluent API for decorators

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;
}

Motivation

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Label provider function

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));

Find a new name for this project

tsdv-joi is not memorable, nor does it roll off the tongue. Let's rename the library, and go with the obvious option joi-decorators (seems it's already taken, let's find a new name).

  • Rename code & doc references
  • Rename the GitHub repository
  • Publish a deprecation notice to npm

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.