type-fest#Class TypeScript Examples

The following examples show how to use type-fest#Class. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: decorators.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
/**
 * A helper function for monitors.
 * @param id
 * @param options
 */
export function monitor(id: string, options?: MonitorOptions) {
  return <T extends Class<Monitor>>(target: T) => {
    return class extends target {
      constructor(...args: any[]) {
        super(...args, id, options);
      }
    };
  };
}
Example #2
Source File: database.ts    From ZenTS with MIT License 6 votes vote down vote up
function repositoryDecorator(
  repositoryType: REPOSITORY_TYPE,
  entity: Class,
): (target: Class, propertyKey: string, parameterIndex: number) => void {
  return (target: Class, propertyKey: string, parameterIndex: number): void => {
    const repositories =
      (Reflect.getMetadata(
        REFLECT_METADATA.DATABASE_REPOSITORY,
        target,
        propertyKey,
      ) as RepositoryReflectionMetadata[]) ?? []

    repositories.push({
      index: parameterIndex,
      propertyKey,
      entity,
      repositoryType,
    })

    Reflect.defineMetadata(REFLECT_METADATA.DATABASE_REPOSITORY, repositories, target, propertyKey)
  }
}
Example #3
Source File: decorators.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
/**
 * A helper function for commands.
 * @param id
 * @param options
 */
export function listener(id: string, options: ListenerOptions) {
  return <T extends Class<Listener>>(target: T) => {
    return class extends target {
      constructor(...args: any[]) {
        void args;
        super(id, options);
      }
    };
  };
}
Example #4
Source File: security.ts    From ZenTS with MIT License 6 votes vote down vote up
export function securityProvider(provider: string = 'default') {
  return (target: Class, propertyKey: string, parameterIndex: number): void => {
    const providers =
      (Reflect.getMetadata(
        REFLECT_METADATA.SECURITY_PROVIDER,
        target,
        propertyKey,
      ) as SecurityProviderReflectionMetadata[]) ?? []

    providers.push({
      index: parameterIndex,
      propertyKey,
      target,
      name: provider,
    })

    Reflect.defineMetadata(REFLECT_METADATA.SECURITY_PROVIDER, providers, target, propertyKey)
  }
}
Example #5
Source File: security.ts    From ZenTS with MIT License 6 votes vote down vote up
export function session(provider: string = 'default') {
  return (target: Class, propertyKey: string, parameterIndex: number): void => {
    const sessions =
      (Reflect.getMetadata(
        REFLECT_METADATA.SESSION,
        target,
        propertyKey,
      ) as SecurityProviderReflectionMetadata[]) ?? []

    sessions.push({
      index: parameterIndex,
      propertyKey,
      target,
      name: provider,
    })

    Reflect.defineMetadata(REFLECT_METADATA.SESSION, sessions, target, propertyKey)
  }
}
Example #6
Source File: Injector.ts    From ZenTS with MIT License 6 votes vote down vote up
public inject<T>(module: Class, ctorArgs: unknown[]): T {
    const instance: InjectModuleInstance = new module(...ctorArgs)
    const actions = [
      new DependenciesAction(this),
      new ConnectionAction(this),
      new RedisAction(this),
      new EntityManagerAction(this),
    ]

    for (const action of actions) {
      action.run(instance)
    }

    return instance as T
  }
Example #7
Source File: SecurityProviderLoader.ts    From ZenTS with MIT License 6 votes vote down vote up
protected getEntities(
    entities: Entities,
    providerConfig: SecurityProviderOption,
  ): SecurityProviderOptionEntities {
    const user =
      typeof providerConfig.entity === 'string'
        ? entities.get(providerConfig.entity.toLowerCase())
        : null

    let dbStore: Class

    if (providerConfig.store?.type === 'database') {
      dbStore = entities.get(providerConfig.store?.entity.toLowerCase())
    }

    return {
      user,
      dbStore,
    }
  }
Example #8
Source File: Akairo.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
AkairoHandler.prototype.load = function (
  thing: string | typeof AkairoModule,
  isReload: boolean
): AkairoModule {
  let Module: Class<AkairoModule>;
  if (typeof thing === "function") {
    Module = thing;
  } else {
    if (!this.extensions.has(extname(thing))) {
      return {} as AkairoModule;
    }

    const exported = this.findExport(require(thing));
    delete require.cache[require.resolve(thing)];

    if (!exported) {
      return {} as AkairoModule;
    }

    Module = exported;
  }

  if (!Module || !(Module.prototype instanceof this.classToHandle)) {
    return {} as AkairoModule;
  }

  const module: AkairoModule = new Module();
  if (this.modules.has(module.id)) {
    // @ts-expect-error
    throw new AkairoError("ALREADY_LOADED", this.classToHandle.name, module.id);
  }

  this.register(module, typeof thing === "function" ? undefined : thing);
  this.emit("load", module, isReload);

  return module;
};
Example #9
Source File: Akairo.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
AkairoHandler.prototype.findExport = function (
  module: Dictionary
): Class<AkairoModule> | null {
  if (module.__esModule) {
    const _default = Reflect.get(module, "default");
    if (isClass(_default)) {
      return _default as Class<AkairoModule>;
    }

    let _class: Class | null = null;
    for (const prop of Object.values(module)) {
      if (isClass(prop)) {
        _class = prop;
        break;
      }
    }

    return _class as Class<AkairoModule> | null;
  }

  return isClass(module) ? (module as Class<AkairoModule>) : null;
};
Example #10
Source File: Functions.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
/**
 * A helper function for determining whether something is a class.
 * @param input
 */
export function isClass(input: unknown): input is Class<unknown> {
  return (
    typeof input === "function" &&
    typeof input.prototype === "object" &&
    input.toString().substring(0, 5) === "class"
  );
}
Example #11
Source File: decorators.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
/**
 * A helper function for commands.
 * @param id
 * @param options
 */
export function command(id: string, options?: MandrocCommandOptions) {
  return <T extends Class<MandrocCommand>>(target: T) => {
    return class extends target {
      constructor(...args: any[]) {
        void args;
        super(id, options);
      }
    };
  };
}
Example #12
Source File: decorators.ts    From Mandroc with GNU General Public License v3.0 6 votes vote down vote up
/**
 * A helper function for inhibitors.
 * @param id
 * @param options
 */
export function inhibitor(id: string, options?: InhibitorOptions) {
  return <T extends Class<Inhibitor>>(target: T) => {
    return class extends target {
      constructor(...args: any[]) {
        super(...args, id, options);
      }
    };
  };
}
Example #13
Source File: routing.ts    From ZenTS with MIT License 5 votes vote down vote up
export function prefix(path: string) {
  return (target: Class): void => {
    Reflect.defineMetadata(REFLECT_METADATA.URL_PREFIX, path, target)
  }
}
Example #14
Source File: inject.ts    From ZenTS with MIT License 5 votes vote down vote up
export function inject(target: any, propertyKey: string): void {
  const dependency: Class = Reflect.getMetadata('design:type', target, propertyKey) as Class
  const dependencies: ModuleDependency[] =
    (Reflect.getMetadata(REFLECT_METADATA.DEPENDENCIES, target) as ModuleDependency[]) ?? []
  dependencies.push({ propertyKey, dependency })

  Reflect.defineMetadata(REFLECT_METADATA.DEPENDENCIES, dependencies, target)
}
Example #15
Source File: email.ts    From ZenTS with MIT License 5 votes vote down vote up
export function email(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.EMAIL, parameterIndex, target, propertyKey)
}
Example #16
Source File: database.ts    From ZenTS with MIT License 5 votes vote down vote up
export function customRepository(
  repository: Class,
): (target: Class, propertyKey: string, parameterIndex: number) => void {
  return repositoryDecorator(REPOSITORY_TYPE.CUSTOM, repository)
}
Example #17
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function error(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_ERROR, parameterIndex, target, propertyKey)
}
Example #18
Source File: database.ts    From ZenTS with MIT License 5 votes vote down vote up
export function treeRepository(
  entity: Class,
): (target: Class, propertyKey: string, parameterIndex: number) => void {
  return repositoryDecorator(REPOSITORY_TYPE.TREE, entity)
}
Example #19
Source File: database.ts    From ZenTS with MIT License 5 votes vote down vote up
export function repository(
  entity: Class,
): (target: Class, propertyKey: string, parameterIndex: number) => void {
  return repositoryDecorator(REPOSITORY_TYPE.REPOSITORY, entity)
}
Example #20
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function context(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_ALL, parameterIndex, target, propertyKey)
}
Example #21
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function response(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_RESPONSE, parameterIndex, target, propertyKey)
}
Example #22
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function request(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_REQUEST, parameterIndex, target, propertyKey)
}
Example #23
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function cookie(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_COOKIE, parameterIndex, target, propertyKey)
}
Example #24
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function params(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_PARAMS, parameterIndex, target, propertyKey)
}
Example #25
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function query(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_QUERY, parameterIndex, target, propertyKey)
}
Example #26
Source File: context.ts    From ZenTS with MIT License 5 votes vote down vote up
export function body(target: Class, propertyKey: string, parameterIndex: number): void {
  Reflect.defineMetadata(REFLECT_METADATA.CONTEXT_BODY, parameterIndex, target, propertyKey)
}
Example #27
Source File: ControllerLoader.ts    From ZenTS with MIT License 5 votes vote down vote up
protected loadControllerRoutes(classModule: Class): Route[] {
    const routes: Route[] = []
    const methods = this.getClassMethods(classModule.prototype)
    let prefix = Reflect.getMetadata(REFLECT_METADATA.URL_PREFIX, classModule) as string

    if (!prefix) {
      prefix = ''
    } else if (prefix.endsWith('/')) {
      prefix = prefix.slice(0, -1)
    }

    for (const method of methods) {
      if (method === 'constructor') {
        continue
      }

      const httpMethod = Reflect.getMetadata(
        REFLECT_METADATA.HTTP_METHOD,
        classModule.prototype,
        method,
      ) as HTTPMethod

      if (typeof httpMethod !== 'string') {
        continue
      }

      const route: Route = {
        method: httpMethod.toUpperCase() as HTTPMethod,
        path: '',
        controllerMethod: method,
      }

      let urlPath = Reflect.getMetadata(
        REFLECT_METADATA.URL_PATH,
        classModule.prototype,
        method,
      ) as string

      if (prefix.length && !urlPath.startsWith('/')) {
        urlPath = `/${urlPath}`
      }

      route.path = `${prefix}${urlPath}`

      const authProvider = Reflect.getMetadata(
        REFLECT_METADATA.AUTH_PROVIDER,
        classModule.prototype,
        method,
      ) as string

      if (typeof authProvider === 'string') {
        route.authProvider = authProvider
      }

      const validationSchema = Reflect.getMetadata(
        REFLECT_METADATA.VALIDATION_SCHEMA,
        classModule.prototype,
        method,
      ) as ValidationSchema

      if (Joi.isSchema(validationSchema)) {
        route.validationSchema = validationSchema
      }

      routes.push(route)
    }

    return routes
  }
Example #28
Source File: basic.spec.ts    From nestjs-joi with MIT License 4 votes vote down vote up
describe('basic integration', () => {
  describe('with schema as argument', () => {
    it('should validate against the schema', async () => {
      const pipe = new JoiPipe(
        Joi.object().keys({
          prop: Joi.string(),
        }),
      );

      try {
        pipe.transform(
          {
            prop: 1,
          },
          { type: 'query' },
        );
        throw new Error('should not be thrown');
      } catch (error) {
        expect(error.message).toContain('"prop" must be a string');
      }
    });

    it('should validate against the schema, ignoring groups (positive test)', async () => {
      const pipe = new JoiPipe(
        Joi.object().keys({
          prop: Joi.string(),
        }),
        { group: 'group1' },
      );

      let error;
      try {
        pipe.transform(
          {
            prop: '1',
          },
          { type: 'query' },
        );
      } catch (error_) {
        error = error_;
      }

      expect(error).toBeUndefined();
    });

    it('should validate against the schema, ignoring groups (negative test)', async () => {
      const pipe = new JoiPipe(
        Joi.object().keys({
          prop: Joi.string(),
        }),
        { group: 'group1' },
      );

      try {
        pipe.transform(
          {
            prop: 1,
          },
          { type: 'query' },
        );
        throw new Error('should not be thrown');
      } catch (error) {
        expect(error.message).toContain('"prop" must be a string');
      }
    });

    it('should ignore a metatype passed in the metadata', async () => {
      const pipe = new JoiPipe(
        Joi.object().keys({
          prop: Joi.string(),
        }),
      );

      class metatype {
        @JoiSchema(Joi.number())
        prop!: number;
      }

      try {
        pipe.transform(
          {
            prop: 1,
          },
          { type: 'query', metatype },
        );
        throw new Error('should not be thrown');
      } catch (error) {
        expect(error.message).toContain('"prop" must be a string');
      }
    });
  });

  const CASES: {
    [name: string]: {
      fit?: boolean;
      type: Class;
      opts: { group?: string };
      payload: unknown;
      expectErrors: string[];
      notExpectErrors: string[];
    };
  } = {
    'schema constructed from empty type': {
      type: EmptyType,
      opts: {},
      payload: {},
      expectErrors: [],
      notExpectErrors: [],
    },
    'schema constructed from basic type': {
      type: BasicType,
      opts: {},
      payload: {
        prop1: 'foo',
      },
      expectErrors: ['"prop1" must be [basic_prop1]', '"prop2" is required'],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from extended type': {
      type: ExtendedType,
      opts: {},
      payload: {
        prop1: 'foo',
        prop2: 'foo',
      },
      expectErrors: [
        '"prop1" must be [basic_prop1]',
        '"prop2" must be [extended_prop2]',
        '"extendedProp" is required',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from decorator-extended type': {
      type: DecoratorExtendedType,
      opts: {},
      payload: {
        prop1: 'foo',
        prop2: 'foo',
      },
      expectErrors: [
        '"prop1" must be [basic_prop1]',
        '"prop2" must be [decorator_extended_prop2]',
        '"extendedProp" is required',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from basic type, respecting groups': {
      type: BasicType,
      opts: { group: 'group1' },
      payload: {
        prop1: 'foo',
        prop2: 'foo',
      },
      expectErrors: [
        '"prop1" must be [basic_prop1_group1]',
        '"prop2" must be [basic_prop2_group1]',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from basic type, falling back to default group': {
      type: BasicType,
      opts: { group: 'groupX' },
      payload: {
        prop1: 'foo',
        prop2: 'foo',
      },
      expectErrors: ['"prop1" must be [basic_prop1]', '"prop2" must be [basic_prop2]'],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from extended type, respecting groups': {
      type: ExtendedType,
      opts: { group: 'group1' },
      payload: {
        prop1: 'foo',
        prop2: 'foo',
        extendedProp: 'foo',
      },
      expectErrors: [
        '"prop1" must be [basic_prop1_group1]',
        '"prop2" must be [extended_prop2_group1]',
        '"extendedProp" must be [extended_extendedProp_group1]',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from decorator-extended type, respecting groups': {
      type: DecoratorExtendedType,
      opts: { group: 'group1' },
      payload: {
        prop1: 'foo',
        prop2: 'foo',
        extendedProp: 'foo',
      },
      expectErrors: [
        '"prop1" must be [basic_prop1_group1]',
        '"prop2" must be [decorator_extended_prop2_group1]',
        '"extendedProp" must be [decorator_extended_extendedProp_group1]',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from advanced type (positive, alternative 1)': {
      type: AdvancedType,
      opts: {},
      payload: {
        prop: {
          prop1: 'basic_prop1',
          prop2: 'basic_prop2',
        },
      },
      expectErrors: [],
      notExpectErrors: ['"prop"'],
    },
    'schema constructed from advanced type (positive, alternative 2)': {
      type: AdvancedType,
      opts: {},
      payload: {
        prop: {
          prop1: 'basic_prop1',
          prop2: 'extended_prop2',
          extendedProp: 'extended_extendedProp',
        },
      },
      expectErrors: [],
      notExpectErrors: ['"prop"'],
    },
    'schema constructed from advanced type (negative)': {
      type: AdvancedType,
      opts: {},
      payload: {
        prop: {
          prop1: 'basic_prop1',
        },
      },
      expectErrors: ['"prop" does not match any of the allowed types'],
      notExpectErrors: [],
    },
    'schema constructed from basic type with options': {
      type: BasicTypeWithOptions,
      opts: {},
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: ['"unknownProp" is not allowed'],
      notExpectErrors: [],
    },
    'schema constructed from extended type with options': {
      type: ExtendedTypeWithOptions,
      opts: {},
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: [],
      notExpectErrors: [],
    },
    'schema constructed from decorator-extended type with options': {
      type: DecoratorExtendedTypeWithOptions,
      opts: {},
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: [],
      notExpectErrors: [],
    },
    'schema constructed from basic type with options, respecting groups': {
      type: BasicTypeWithOptions,
      opts: { group: 'group1' },
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: [],
      notExpectErrors: [],
    },
    'schema constructed from extended type with options, respecting groups': {
      type: ExtendedTypeWithOptions,
      opts: { group: 'group1' },
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: ['"unknownProp" is not allowed'],
      notExpectErrors: [],
    },
    'schema constructed from decorator-extended type with options, respecting groups': {
      type: DecoratorExtendedTypeWithOptions,
      opts: { group: 'group1' },
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: ['"unknownProp" is not allowed'],
      notExpectErrors: [],
    },
    'schema constructed from basic type with no default options': {
      type: BasicTypeWithNoDefaultOptions,
      opts: {},
      payload: {
        prop1: 'string',
        prop2: 'string',
      },
      expectErrors: [
        '"prop1" must be [basicwithnodefaultoptions_prop1]',
        '"prop2" must be [basicwithnodefaultoptions_prop2]',
      ],
      notExpectErrors: [],
    },
    'schema constructed from basic type with no default options, respecting groups': {
      type: BasicTypeWithNoDefaultOptions,
      opts: { group: 'group1' },
      payload: {
        prop1: 'string',
        prop2: 'string',
      },
      expectErrors: ['"prop1" must be [basicwithnodefaultoptions_prop1_group1]'],
      notExpectErrors: ['"prop2" must be [basicwithnodefaultoptions_prop2_group1]'],
    },
    'schema constructed from type with nested type (negative)': {
      type: TypeWithNestedType,
      opts: {},
      payload: {
        prop0: 'string',
        prop1: 'string',
        nestedProp: {
          prop1: 'string',
          prop2: 'string',
        },
      },
      expectErrors: [
        '"prop1" must be [nested_prop1]',
        '"nestedProp.prop1" must be [basic_prop1]',
        '"nestedProp.prop2" must be [basic_prop2]',
      ],
      notExpectErrors: ['"prop0"', 'nestedTypeWithFn'],
    },
    'schema constructed from type with nested type (positive)': {
      type: TypeWithNestedType,
      opts: {},
      payload: {
        prop0: 'string',
        prop1: 'nested_prop1',
        nestedProp: {
          prop1: 'basic_prop1',
          prop2: 'basic_prop2',
        },
      },
      expectErrors: [],
      notExpectErrors: ['"prop0"', '"prop1"', '"nestedProp.prop1"', '"nestedProp.prop2"'],
    },
    'schema constructed from type with nested type, respecting groups (negative)': {
      type: TypeWithNestedType,
      opts: { group: 'group1' },
      payload: {
        prop0: 'string',
        prop1: 'string',
        nestedProp: {
          prop1: 'string',
          prop2: 'string',
          extendedProp: 'string',
        },
      },
      expectErrors: [
        '"prop1" must be [nested_prop1_group1]',
        '"nestedProp.prop1" must be [basic_prop1_group1]',
        '"nestedProp.prop2" must be [extended_prop2_group1]',
        '"nestedProp.extendedProp" must be [extended_extendedProp_group1]',
      ],
      notExpectErrors: ['"prop0"', 'nestedTypeWithFn'],
    },
    'schema constructed from type with nested type, respecting groups (positive)': {
      type: TypeWithNestedType,
      opts: { group: 'group1' },
      payload: {
        prop0: 'string',
        prop1: 'nested_prop1_group1',
        nestedProp: {
          prop1: 'basic_prop1_group1',
          prop2: 'extended_prop2_group1',
          extendedProp: 'extended_extendedProp_group1',
        },
      },
      expectErrors: [],
      notExpectErrors: [
        '"prop0"',
        '"prop1"',
        '"nestedProp.prop1"',
        '"nestedProp.prop2"',
        '"nestedProp.extendedProp"',
      ],
    },

    'schema constructed from type with nested type array (negative)': {
      type: TypeWithNestedTypeArray,
      opts: {},
      payload: {
        prop0: 'string',
        prop1: 'string',
        nestedProp: [
          {
            prop1: 'string',
            prop2: 'string',
          },
        ],
      },
      expectErrors: [
        '"prop1" must be [nested_array_prop1]',
        '"nestedProp[0].prop1" must be [basic_prop1]',
        '"nestedProp[0].prop2" must be [basic_prop2]',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from type with nested type array (positive)': {
      type: TypeWithNestedTypeArray,
      opts: {},
      payload: {
        prop0: 'string',
        prop1: 'nested_array_prop1',
        nestedProp: [
          {
            prop1: 'basic_prop1',
            prop2: 'basic_prop2',
          },
        ],
      },
      expectErrors: [],
      notExpectErrors: [
        '"prop0"',
        '"prop1"',
        '"nestedProp[0].prop1"',
        '"nestedProp[0].prop2"',
        '"nestedProp[0].extendedProp"',
      ],
    },
    'schema constructed from type with nested type array, respecting groups (negative)': {
      type: TypeWithNestedTypeArray,
      opts: { group: 'group1' },
      payload: {
        prop0: 'string',
        prop1: 'string',
        nestedProp: [
          {
            prop1: 'string',
            prop2: 'string',
            extendedProp: 'string',
          },
        ],
      },
      expectErrors: [
        '"prop1" must be [nested_array_prop1_group1]',
        '"nestedProp[0].prop1" must be [basic_prop1_group1]',
        '"nestedProp[0].prop2" must be [extended_prop2_group1]',
        '"nestedProp[0].extendedProp" must be [extended_extendedProp_group1]',
      ],
      notExpectErrors: ['"prop0"'],
    },
    'schema constructed from type with nested type array, respecting groups (positive)': {
      type: TypeWithNestedTypeArray,
      opts: { group: 'group1' },
      payload: {
        prop0: 'string',
        prop1: 'nested_array_prop1_group1',
        nestedProp: [
          {
            prop1: 'basic_prop1_group1',
            prop2: 'extended_prop2_group1',
            extendedProp: 'extended_extendedProp_group1',
          },
        ],
      },
      expectErrors: [],
      notExpectErrors: [
        '"prop0"',
        '"prop1"',
        '"nestedProp[0].prop1"',
        '"nestedProp[0].prop2"',
        '"nestedProp[0].extendedProp"',
      ],
    },
    'schema constructed from type with nested type and customizer (positive)': {
      type: TypeWithNestedTypeAndCustomizer,
      opts: {},
      payload: {
        nestedProp: undefined,
      },
      expectErrors: [],
      notExpectErrors: ['"nestedProp"'],
    },
    'schema constructed from type with nested type and customizer (negative)': {
      type: TypeWithNestedTypeAndCustomizer,
      opts: {},
      payload: {
        nestedProp: {
          prop1: 'string',
          prop2: 'string',
        },
      },
      expectErrors: [
        '"nestedProp.prop1" must be [basic_prop1]',
        '"nestedProp.prop2" must be [basic_prop2]',
      ],
      notExpectErrors: [],
    },
    'schema constructed from type with nested type and customizer, respecting groups (negative)': {
      type: TypeWithNestedTypeAndCustomizer,
      opts: { group: 'group1' },
      payload: {
        nestedProp: undefined,
      },
      expectErrors: ['"nestedProp" is required'],
      notExpectErrors: [],
    },
    'schema constructed from type with nested type and customizer, respecting groups (positive)': {
      type: TypeWithNestedTypeAndCustomizer,
      opts: { group: 'group1' },
      payload: {
        nestedProp: {
          prop1: 'basic_prop1_group1',
          prop2: 'basic_prop2_group1',
        },
      },
      expectErrors: [],
      notExpectErrors: ['"nestedProp"'],
    },
    'schema constructed from type with nested type array and array customizer (positive)': {
      type: TypeWithNestedTypeArrayAndArrayCustomizer,
      opts: {},
      payload: {
        nestedProp: undefined,
      },
      expectErrors: [],
      notExpectErrors: ['"nestedProp"'],
    },
    'schema constructed from type with nested type array and array customizer (negative)': {
      type: TypeWithNestedTypeArrayAndArrayCustomizer,
      opts: {},
      payload: {
        nestedProp: [
          {
            prop1: 'string',
            prop2: 'string',
          },
        ],
      },
      expectErrors: [
        '"nestedProp[0].prop1" must be [basic_prop1]',
        '"nestedProp[0].prop2" must be [basic_prop2]',
      ],
      notExpectErrors: [],
    },
    'schema constructed from type with nested type array and array customizer, respecting groups (negative)':
      {
        type: TypeWithNestedTypeArrayAndArrayCustomizer,
        opts: { group: 'group1' },
        payload: {
          nestedProp: undefined,
        },
        expectErrors: ['"nestedProp" is required'],
        notExpectErrors: [],
      },
    'schema constructed from type with nested type array and array customizer, respecting groups (positive)':
      {
        type: TypeWithNestedTypeArrayAndArrayCustomizer,
        opts: { group: 'group1' },
        payload: {
          nestedProp: [
            {
              prop1: 'basic_prop1_group1',
              prop2: 'basic_prop2_group1',
            },
          ],
        },
        expectErrors: [],
        notExpectErrors: ['"nestedProp"'],
      },
    'schema constructed from type with nested type array and customizer (positive)': {
      type: TypeWithNestedTypeArrayAndCustomizer,
      opts: {},
      payload: {
        nestedProp: [],
      },
      expectErrors: [],
      notExpectErrors: ['"nestedProp"'],
    },
    'schema constructed from type with nested type array and customizer (negative)': {
      type: TypeWithNestedTypeArrayAndCustomizer,
      opts: {},
      payload: {
        nestedProp: [
          {
            prop1: 'string',
            prop2: 'string',
          },
        ],
      },
      expectErrors: [
        '"nestedProp[0].prop1" must be [basic_prop1]',
        '"nestedProp[0].prop2" must be [basic_prop2]',
      ],
      notExpectErrors: [],
    },
    'schema constructed from type with nested type array and customizer, respecting groups (negative)':
      {
        type: TypeWithNestedTypeArrayAndCustomizer,
        opts: { group: 'group1' },
        payload: {
          nestedProp: [],
        },
        expectErrors: ['"nestedProp" does not contain 1 required value'],
        notExpectErrors: [],
      },
    'schema constructed from type with nested type array and customizer, respecting groups (positive)':
      {
        type: TypeWithNestedTypeArrayAndCustomizer,
        opts: { group: 'group1' },
        payload: {
          nestedProp: [
            {
              prop1: 'basic_prop1_group1',
              prop2: 'basic_prop2_group1',
            },
          ],
        },
        expectErrors: [],
        notExpectErrors: ['"nestedProp"'],
      },
    'nothing: empty schema without @JoiSchema': {
      type: EmptyType,
      opts: {},
      payload: {
        prop: 'basicwithoptions_prop',
        unknownProp: 'string',
      },
      expectErrors: [],
      notExpectErrors: [],
    },
    // Special inbuilt types: construct cases
    ...fromPairs(
      [String, Object, Number, Array].map(type => [
        'nothing: handle inbuilt type ' + type.name,
        {
          type,
          opts: {},
          payload: {
            prop: 'basicwithoptions_prop',
            unknownProp: 'string',
          },
          expectErrors: [],
        },
      ]),
    ),
  };

  for (const mode of ['type', 'metatype']) {
    describe('with ' + mode + ' as argument', () => {
      for (const [
        caseName,
        { fit: useFit, type, opts, payload, expectErrors, notExpectErrors },
      ] of Object.entries(CASES)) {
        (useFit ? fit : it)('should validate against ' + caseName, async () => {
          const pipe = mode === 'type' ? new JoiPipe(type, opts) : new JoiPipe(opts);

          let error_;
          try {
            pipe.transform(payload, {
              type: 'query',
              metatype: mode === 'metatype' ? type : undefined,
            });

            if (expectErrors && expectErrors.length) {
              throw new Error('should not be thrown');
            }
          } catch (error) {
            error_ = error;

            if (expectErrors && expectErrors.length) {
              expectErrors.map(x => expect(error.stack).toContain(x));
            }
            if (notExpectErrors && notExpectErrors.length) {
              notExpectErrors.map(x => expect(error.stack).not.toContain(x));
            }
          }

          if (!expectErrors || !expectErrors.length) {
            expect(error_).toBeUndefined();
          }
        });
      }
    });
  }
});