Advertisement
Guest User

Untitled

a guest
Apr 29th, 2019
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.20 KB | None | 0 0
  1. const log = (a: any) => console.log(JSON.stringify(a, null, 2));
  2.  
  3. // implementation
  4.  
  5. interface RecordErrors {
  6. [k: string]: Array<string | RecordErrors>;
  7. }
  8.  
  9. type Errors = Array<string | RecordErrors>;
  10.  
  11. type Validator<From, To> = (value: From) => Check<To>;
  12.  
  13. type RecordValidator<T extends {}> = {
  14. [K in keyof T]: Validator<unknown, T[K]>
  15. };
  16.  
  17. type ValidateRecord<T extends {}> = Check<T>;
  18.  
  19. type ValidatorType<
  20. T extends (value: unknown) => ValidateRecord<any>
  21. > = T extends (value: unknown) => ValidateRecord<infer U> ? U : never;
  22.  
  23. type Check<T> = { value: T; errors?: undefined } | { errors: Errors };
  24.  
  25. function validateRecord<T extends {}>(
  26. record: RecordValidator<T>,
  27. ): (value: unknown) => ValidateRecord<T> {
  28. return value => {
  29. const output: Partial<T> = {};
  30.  
  31. if (typeof value !== 'object') {
  32. return { errors: [`Unexpected ${typeof value}`] };
  33. }
  34.  
  35. if (value === null) {
  36. return { errors: ['Unexpected null'] };
  37. }
  38.  
  39. const errors: RecordErrors = {};
  40.  
  41. for (const k of Object.keys(record) as Array<keyof T>) {
  42. const field = value[k as keyof typeof value];
  43. const result = record[k](field);
  44.  
  45. if (result.errors) {
  46. errors[k as string] = (errors[k as string] || []).concat(result.errors);
  47. continue;
  48. }
  49.  
  50. output[k] = result.value !== undefined ? result.value : field;
  51. }
  52.  
  53. if (Object.keys(errors).length > 0) {
  54. return { errors: [errors] };
  55. }
  56.  
  57. return { value: output as T };
  58. };
  59. }
  60.  
  61. function compose<To, From = unknown>(
  62. typecheck: Validator<From, To>,
  63. ...passes: Array<Validator<To, To>>
  64. ): (value: From) => Check<To> {
  65. return (value: From): Check<To> => {
  66. const first = typecheck(value);
  67. if (first.errors) return first;
  68.  
  69. let step: To = first.value !== undefined ? first.value : (value as any);
  70.  
  71. for (const validate of passes) {
  72. const result = validate(step);
  73. if (result.errors) return result;
  74. step = result.value !== undefined ? result.value : step;
  75. }
  76.  
  77. return { value: step };
  78. };
  79. }
  80.  
  81. // validators
  82.  
  83. const isString = (value: unknown): Check<string> =>
  84. typeof value === 'string' ? { value } : { errors: ['is not a string'] };
  85.  
  86. const isBoolean = (value: unknown): Check<boolean> =>
  87. typeof value === 'boolean' ? { value } : { errors: ['is not a boolean'] };
  88.  
  89. const isNumber = (value: unknown): Check<number> =>
  90. typeof value === 'number' ? { value } : { errors: ['is not a number'] };
  91.  
  92. const isArray = <T>(check: (value: unknown) => Check<T>) => (
  93. values: unknown,
  94. ): Check<T[]> => {
  95. if (!Array.isArray(values)) {
  96. return { errors: ['is not an array'] };
  97. }
  98.  
  99. const results = values.map(check);
  100. const error = results.filter(({ errors }) => errors)[0];
  101.  
  102. if (error && error.errors) {
  103. return { errors: error.errors };
  104. }
  105.  
  106. const successes = results as Array<{ value: T }>;
  107.  
  108. return {
  109. value: successes.map(result => result.value),
  110. };
  111. };
  112.  
  113. const minLength = <T extends { length: number }>(length: number) => (
  114. value: T,
  115. ): Check<T> =>
  116. value.length >= length
  117. ? { value }
  118. : { errors: [`expected length to be greater or equal to ${length}`] };
  119.  
  120. const inRange = (from: number, to: number = Infinity) => (
  121. value: number,
  122. ): Check<number> =>
  123. value >= from && value < to
  124. ? { value }
  125. : { errors: [`expected to be inside of ${from}..${to} range`] };
  126.  
  127. // transformers
  128.  
  129. const capitalize = (value: string): Check<string> => ({
  130. value: value.charAt(0).toUpperCase() + value.slice(1),
  131. });
  132.  
  133. // usage
  134.  
  135. const validateOrder = validateRecord({
  136. title: isString,
  137. });
  138.  
  139. const validateName = compose(
  140. isString,
  141. capitalize,
  142. );
  143.  
  144. const validateUser = validateRecord({
  145. id: isString,
  146. name: validateName,
  147. password: compose(
  148. isString,
  149. minLength(6),
  150. ),
  151. active: isBoolean,
  152. age: compose(
  153. isNumber,
  154. inRange(1, 200),
  155. ),
  156. order: validateOrder,
  157. orders: isArray<Order>(validateOrder),
  158. orders1: compose<Order[]>(
  159. isArray(validateOrder),
  160. minLength(1),
  161. ),
  162. });
  163.  
  164. type Order = ValidatorType<typeof validateOrder>;
  165. type User = ValidatorType<typeof validateUser>;
  166.  
  167. type a = User['orders1'];
  168.  
  169. log(
  170. validateUser({
  171. id: '1',
  172. name: 'nome',
  173. password: '123456',
  174. active: true,
  175. age: 10,
  176. order: { title: 'Order Title' },
  177. orders: [],
  178. orders1: [{ title: 'Order Title' }],
  179. }),
  180. );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement