diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index 0ecce4e9a..f707c81b1 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -3,7 +3,7 @@ /** * Module dependencies */ -import validate from '../../validator'; +import it from '../../it'; import parse from '../../../common/text'; import Post from '../../models/post'; import { isValidText } from '../../models/post'; @@ -27,13 +27,11 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => { // Get 'text' parameter - const [text, textErr] = validate(params.text, 'string', false, isValidText); + const [text, textErr] = it(params.text).must.be.a.string().validate(isValidText).get(); if (textErr) return rej('invalid text'); // Get 'media_ids' parameter - const [mediaIds, mediaIdsErr] = validate(params.media_ids, 'set', false, - x => x.length > 4 ? 'too many media' : true - ); + const [mediaIds, mediaIdsErr] = it(params.media_ids).must.be.an.array().unique().range(1, 4).get(); if (mediaIdsErr) return rej('invalid media_ids'); let files = []; @@ -42,7 +40,7 @@ module.exports = (params, user, app) => // forEach だと途中でエラーなどがあっても return できないので // 敢えて for を使っています。 for (let i = 0; i < mediaIds.length; i++) { - const [mediaId, mediaIdErr] = validate(mediaIds[i], 'id', true); + const [mediaId, mediaIdErr] = it(mediaIds[i]).must.be.an.id().required().get(); if (mediaIdErr) return rej('invalid media id'); // Fetch file @@ -65,7 +63,7 @@ module.exports = (params, user, app) => } // Get 'repost_id' parameter - const [repostId, repostIdErr] = validate(params.repost_id, 'id'); + const [repostId, repostIdErr] = it(params.repost_id).must.be.an.id().get(); if (repostIdErr) return rej('invalid repost_id'); let repost = null; @@ -107,7 +105,7 @@ module.exports = (params, user, app) => } // Get 'in_reply_to_post_id' parameter - const [inReplyToPostId, inReplyToPostIdErr] = validate(params.reply_to_id, 'id'); + const [inReplyToPostId, inReplyToPostIdErr] = it(params.reply_to_id, 'id'); if (inReplyToPostIdErr) return rej('invalid in_reply_to_post_id'); let inReplyToPost = null; @@ -128,12 +126,12 @@ module.exports = (params, user, app) => } // Get 'poll' parameter - const [_poll, pollErr] = validate(params.poll, 'object'); + const [_poll, pollErr] = it(params.poll, 'object'); if (pollErr) return rej('invalid poll'); let poll = null; if (_poll !== null) { - const [pollChoices, pollChoicesErr] = validate(params.poll, 'set', false, [ + const [pollChoices, pollChoicesErr] = it(params.poll, 'set', false, [ choices => { const shouldReject = choices.some(choice => { if (typeof choice != 'string') return true; diff --git a/src/api/it.ts b/src/api/it.ts new file mode 100644 index 000000000..1aff99c9e --- /dev/null +++ b/src/api/it.ts @@ -0,0 +1,518 @@ +import * as mongo from 'mongodb'; +import hasDuplicates from '../common/has-duplicates'; + +type Validator = (value: T) => boolean | Error; +type Modifier = (value: T) => T; + +interface Factory { + get: () => [any, Error]; + + required: () => Factory; + + validate: (validator: Validator) => Factory; + + modify: (modifier: Modifier) => Factory; +} + +class FactoryCore implements Factory { + value: any; + error: Error; + + constructor() { + this.value = null; + this.error = null; + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + if (this.error === null && this.value === null) { + this.error = new Error('required'); + } + return this; + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any, Error] { + return [this.value, this.error]; + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + if (this.error || this.value === null) return this; + const result = validator(this.value); + if (result === false) { + this.error = new Error('invalid-format'); + } else if (result instanceof Error) { + this.error = result; + } + return this; + } + + modify(modifier: Modifier) { + if (this.error || this.value === null) return this; + try { + this.value = modifier(this.value); + } catch (e) { + this.error = e; + } + return this; + } +} + +class BooleanFactory extends FactoryCore { + value: boolean; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'boolean') { + this.error = new Error('must-be-a-boolean'); + } else { + this.value = value; + } + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [boolean, Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class NumberFactory extends FactoryCore { + value: number; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (!Number.isFinite(value)) { + this.error = new Error('must-be-a-number'); + } else { + this.value = value; + } + } + + /** + * 値が指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ + range(min: number, max: number) { + if (this.error || this.value === null) return this; + if (this.value < min || this.value > max) { + this.error = new Error('invalid-range'); + } + return this; + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [number, Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class StringFactory extends FactoryCore { + value: string; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'string') { + this.error = new Error('must-be-a-string'); + } else { + this.value = value; + } + } + + /** + * 文字数が指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ + range(min: number, max: number) { + if (this.error || this.value === null) return this; + if (this.value.length < min || this.value.length > max) { + this.error = new Error('invalid-range'); + } + return this; + } + + trim() { + if (this.error || this.value === null) return this; + this.value = this.value.trim(); + return this; + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [string, Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class ArrayFactory extends FactoryCore { + value: any[]; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (!Array.isArray(value)) { + this.error = new Error('must-be-an-array'); + } else { + this.value = value; + } + } + + /** + * 配列の値がユニークでない場合(=重複した項目がある場合)エラーにします + */ + unique() { + if (this.error || this.value === null) return this; + if (hasDuplicates(this.value)) { + this.error = new Error('must-be-unique'); + } + return this; + } + + /** + * 配列の長さが指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ + range(min: number, max: number) { + if (this.error || this.value === null) return this; + if (this.value.length < min || this.value.length > max) { + this.error = new Error('invalid-range'); + } + return this; + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any[], Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class IdFactory extends FactoryCore { + value: mongo.ObjectID; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'string' || !mongo.ObjectID.isValid(value)) { + this.error = new Error('must-be-an-id'); + } else { + this.value = new mongo.ObjectID(value); + } + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any[], Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class ObjectFactory extends FactoryCore { + value: any; + error: Error; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'object') { + this.error = new Error('must-be-an-object'); + } else { + this.value = value; + } + } + + /** + * このインスタンスの値が undefined または null の場合エラーにします + */ + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any, Error] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false またはエラーを返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +type MustBe = { + must: { + be: { + a: { + string: () => StringFactory; + number: () => NumberFactory; + boolean: () => BooleanFactory; + }; + an: { + id: () => IdFactory; + array: () => ArrayFactory; + object: () => ObjectFactory; + }; + }; + }; +}; + +const mustBe = (value: any) => ({ + must: { + be: { + a: { + string: () => new StringFactory(value), + number: () => new NumberFactory(value), + boolean: () => new BooleanFactory(value) + }, + an: { + id: () => new IdFactory(value), + array: () => new ArrayFactory(value), + object: () => new ObjectFactory(value) + } + } + } +}); + +type Type = 'id' | 'string' | 'number' | 'boolean' | 'array' | 'set' | 'object'; +type Pipe = (x: T) => T | boolean | Error; + +function validate(value: any, type: 'id', isRequired?: boolean, pipe?: Pipe | Pipe[]): [mongo.ObjectID, Error]; +function validate(value: any, type: 'string', isRequired?: boolean, pipe?: Pipe | Pipe[]): [string, Error]; +function validate(value: any, type: 'number', isRequired?: boolean, pipe?: Pipe | Pipe[]): [number, Error]; +function validate(value: any, type: 'boolean', isRequired?: boolean): [boolean, Error]; +function validate(value: any, type: 'array', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any[], Error]; +function validate(value: any, type: 'set', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any[], Error]; +function validate(value: any, type: 'object', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any, Error]; +function validate(value: any, type: Type, isRequired?: boolean, pipe?: Pipe | Pipe[]): [any, Error] { + if (value === undefined || value === null) { + if (isRequired) { + return [null, new Error('is-required')] + } else { + return [null, null] + } + } + + switch (type) { + case 'id': + if (typeof value != 'string' || !mongo.ObjectID.isValid(value)) { + return [null, new Error('incorrect-id')]; + } + break; + + case 'string': + if (typeof value != 'string') { + return [null, new Error('must-be-a-string')]; + } + break; + + case 'number': + if (!Number.isFinite(value)) { + return [null, new Error('must-be-a-number')]; + } + break; + + case 'boolean': + if (typeof value != 'boolean') { + return [null, new Error('must-be-a-boolean')]; + } + break; + + case 'array': + if (!Array.isArray(value)) { + return [null, new Error('must-be-an-array')]; + } + break; + + case 'set': + if (!Array.isArray(value)) { + return [null, new Error('must-be-an-array')]; + } else if (hasDuplicates(value)) { + return [null, new Error('duplicated-contents')]; + } + break; + + case 'object': + if (typeof value != 'object') { + return [null, new Error('must-be-an-object')]; + } + break; + } + + if (type == 'id') value = new mongo.ObjectID(value); + + if (pipe) { + const pipes = Array.isArray(pipe) ? pipe : [pipe]; + for (let i = 0; i < pipes.length; i++) { + const result = pipes[i](value); + if (result === false) { + return [null, new Error('invalid-format')]; + } else if (result instanceof Error) { + return [null, result]; + } else if (result !== true) { + value = result; + } + } + } + + return [value, null]; +} + +function it(value: any): MustBe; +function it(value: any, type: 'id', isRequired?: boolean, pipe?: Pipe | Pipe[]): [mongo.ObjectID, Error]; +function it(value: any, type: 'string', isRequired?: boolean, pipe?: Pipe | Pipe[]): [string, Error]; +function it(value: any, type: 'number', isRequired?: boolean, pipe?: Pipe | Pipe[]): [number, Error]; +function it(value: any, type: 'boolean', isRequired?: boolean): [boolean, Error]; +function it(value: any, type: 'array', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any[], Error]; +function it(value: any, type: 'set', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any[], Error]; +function it(value: any, type: 'object', isRequired?: boolean, pipe?: Pipe | Pipe[]): [any, Error]; +function it(value: any, type?: any, isRequired?: boolean, pipe?: Pipe | Pipe[]): any { + if (typeof type === 'undefined') { + return mustBe(value); + } else { + return validate(value, type, isRequired, pipe); + } +} + +export default it; diff --git a/src/api/validator.ts b/src/api/validator.ts deleted file mode 100644 index 3f1678e35..000000000 --- a/src/api/validator.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as mongo from 'mongodb'; -import hasDuplicates from '../common/has-duplicates'; - -type Type = 'id' | 'string' | 'number' | 'boolean' | 'array' | 'set' | 'object'; - -type Validator = ((x: T) => boolean | string) | ((x: T) => boolean | string)[]; - -function validate(value: any, type: 'id', isRequired?: boolean): [mongo.ObjectID, string]; -function validate(value: any, type: 'string', isRequired?: boolean, validator?: Validator): [string, string]; -function validate(value: any, type: 'number', isRequired?: boolean, validator?: Validator): [number, string]; -function validate(value: any, type: 'boolean', isRequired?: boolean): [boolean, string]; -function validate(value: any, type: 'array', isRequired?: boolean, validator?: Validator): [any[], string]; -function validate(value: any, type: 'set', isRequired?: boolean, validator?: Validator): [any[], string]; -function validate(value: any, type: 'object', isRequired?: boolean, validator?: Validator): [any, string]; -function validate(value: any, type: Type, isRequired?: boolean, validator?: Validator): [T, string] { - if (value === undefined || value === null) { - if (isRequired) { - return [null, 'is-required'] - } else { - return [null, null] - } - } - - switch (type) { - case 'id': - if (typeof value != 'string' || !mongo.ObjectID.isValid(value)) { - return [null, 'incorrect-id']; - } - break; - - case 'string': - if (typeof value != 'string') { - return [null, 'must-be-a-string']; - } - break; - - case 'number': - if (!Number.isFinite(value)) { - return [null, 'must-be-a-number']; - } - break; - - case 'boolean': - if (typeof value != 'boolean') { - return [null, 'must-be-a-boolean']; - } - break; - - case 'array': - if (!Array.isArray(value)) { - return [null, 'must-be-an-array']; - } - break; - - case 'set': - if (!Array.isArray(value)) { - return [null, 'must-be-an-array']; - } else if (hasDuplicates(value)) { - return [null, 'duplicated-contents']; - } - break; - - case 'object': - if (typeof value != 'object') { - return [null, 'must-be-an-object']; - } - break; - } - - if (type == 'id') value = new mongo.ObjectID(value); - - if (validator) { - const validators = Array.isArray(validator) ? validator : [validator]; - for (let i = 0; i < validators.length; i++) { - const result = validators[i](value); - if (result === false) { - return [null, 'invalid-format']; - } else if (typeof result == 'string') { - return [null, result]; - } - } - } - - return [value, null]; -} - -export default validate; diff --git a/src/api/validator2.ts b/src/api/validator2.ts deleted file mode 100644 index 04dff59aa..000000000 --- a/src/api/validator2.ts +++ /dev/null @@ -1,385 +0,0 @@ -import * as mongo from 'mongodb'; -import hasDuplicates from '../common/has-duplicates'; - -type Validator = (value: T) => boolean | string; -type Modifier = (value: T) => T; - -interface Fuctory { - get: () => [any, string]; - - required: () => Fuctory; - - validate: (validator: Validator) => Fuctory; - - modify: (modifier: Modifier) => Fuctory; -} - -class FuctoryCore implements Fuctory { - value: any; - error: string; - - constructor() { - this.value = null; - this.error = null; - } - - /** - * この値が undefined または null の場合エラーにします - */ - required() { - if (this.error === null && this.value === null) { - this.error = 'required'; - } - return this; - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [any, string] { - return [this.value, this.error]; - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - if (this.error || this.value === null) return this; - const result = validator(this.value); - if (result === false) { - this.error = 'invalid-format'; - } else if (typeof result == 'string') { - this.error = result; - } - return this; - } - - modify(modifier: Modifier) { - if (this.error || this.value === null) return this; - try { - this.value = modifier(this.value); - } catch (e) { - this.error = e; - } - return this; - } -} - -class BooleanFuctory extends FuctoryCore { - value: boolean; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (typeof value != 'boolean') { - this.error = 'must-be-a-boolean'; - } else { - this.value = value; - } - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [boolean, string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -class NumberFuctory extends FuctoryCore { - value: number; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (!Number.isFinite(value)) { - this.error = 'must-be-a-number'; - } else { - this.value = value; - } - } - - /** - * 値が指定された範囲内にない場合エラーにします - * @param min 下限 - * @param max 上限 - */ - range(min: number, max: number) { - if (this.error || this.value === null) return this; - if (this.value < min || this.value > max) { - this.error = 'invalid-range'; - } - return this; - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [number, string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -class StringFuctory extends FuctoryCore { - value: string; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (typeof value != 'string') { - this.error = 'must-be-a-string'; - } else { - this.value = value; - } - } - - /** - * 文字数が指定された範囲内にない場合エラーにします - * @param min 下限 - * @param max 上限 - */ - range(min: number, max: number) { - if (this.error || this.value === null) return this; - if (this.value.length < min || this.value.length > max) { - this.error = 'invalid-range'; - } - return this; - } - - trim() { - if (this.error || this.value === null) return this; - this.value = this.value.trim(); - return this; - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [string, string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -class ArrayFuctory extends FuctoryCore { - value: any[]; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (!Array.isArray(value)) { - this.error = 'must-be-an-array'; - } else { - this.value = value; - } - } - - /** - * 配列の値がユニークでない場合(=重複した項目がある場合)エラーにします - */ - unique() { - if (this.error || this.value === null) return this; - if (hasDuplicates(this.value)) { - this.error = 'must-be-unique'; - } - return this; - } - - /** - * 配列の長さが指定された範囲内にない場合エラーにします - * @param min 下限 - * @param max 上限 - */ - range(min: number, max: number) { - if (this.error || this.value === null) return this; - if (this.value.length < min || this.value.length > max) { - this.error = 'invalid-range'; - } - return this; - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [any[], string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -class IdFuctory extends FuctoryCore { - value: mongo.ObjectID; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (typeof value != 'string' || !mongo.ObjectID.isValid(value)) { - this.error = 'must-be-an-id'; - } else { - this.value = new mongo.ObjectID(value); - } - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [any[], string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -class ObjectFuctory extends FuctoryCore { - value: any; - error: string; - - constructor(value) { - super(); - if (value === undefined || value === null) { - this.value = null; - } else if (typeof value != 'object') { - this.error = 'must-be-an-object'; - } else { - this.value = value; - } - } - - required() { - return super.required(); - } - - /** - * このインスタンスの値およびエラーを取得します - */ - get(): [any, string] { - return super.get(); - } - - /** - * このインスタンスの値に対して妥当性を検証します - * バリデータが false または(エラーを表す)文字列を返した場合エラーにします - * @param validator バリデータ - */ - validate(validator: Validator) { - return super.validate(validator); - } - - modify(modifier: Modifier) { - return super.modify(modifier); - } -} - -const it = (value: any) => ({ - must: { - be: { - a: { - string: () => new StringFuctory(value), - number: () => new NumberFuctory(value), - boolean: () => new BooleanFuctory(value) - }, - an: { - id: () => new IdFuctory(value), - array: () => new ArrayFuctory(value), - object: () => new ObjectFuctory(value) - } - } - } -}); - -export default it; - -const [n, e] = it(42).must.be.a.number().required().range(10, 70).validate(x => x != 21).get();