string.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.parseStringDef = exports.zodPatterns = void 0;
  4. const errorMessages_1 = require("../errorMessages.js");
  5. let emojiRegex;
  6. /**
  7. * Generated from the regular expressions found here as of 2024-05-22:
  8. * https://github.com/colinhacks/zod/blob/master/src/types.ts.
  9. *
  10. * Expressions with /i flag have been changed accordingly.
  11. */
  12. exports.zodPatterns = {
  13. /**
  14. * `c` was changed to `[cC]` to replicate /i flag
  15. */
  16. cuid: /^[cC][^\s-]{8,}$/,
  17. cuid2: /^[0-9a-z]+$/,
  18. ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
  19. /**
  20. * `a-z` was added to replicate /i flag
  21. */
  22. email: /^(?!\.)(?!.*\.\.)([a-zA-Z0-9_'+\-\.]*)[a-zA-Z0-9_+-]@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/,
  23. /**
  24. * Constructed a valid Unicode RegExp
  25. *
  26. * Lazily instantiate since this type of regex isn't supported
  27. * in all envs (e.g. React Native).
  28. *
  29. * See:
  30. * https://github.com/colinhacks/zod/issues/2433
  31. * Fix in Zod:
  32. * https://github.com/colinhacks/zod/commit/9340fd51e48576a75adc919bff65dbc4a5d4c99b
  33. */
  34. emoji: () => {
  35. if (emojiRegex === undefined) {
  36. emojiRegex = RegExp('^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$', 'u');
  37. }
  38. return emojiRegex;
  39. },
  40. /**
  41. * Unused
  42. */
  43. uuid: /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
  44. /**
  45. * Unused
  46. */
  47. ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,
  48. /**
  49. * Unused
  50. */
  51. ipv6: /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/,
  52. base64: /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,
  53. nanoid: /^[a-zA-Z0-9_-]{21}$/,
  54. };
  55. function parseStringDef(def, refs) {
  56. const res = {
  57. type: 'string',
  58. };
  59. function processPattern(value) {
  60. return refs.patternStrategy === 'escape' ? escapeNonAlphaNumeric(value) : value;
  61. }
  62. if (def.checks) {
  63. for (const check of def.checks) {
  64. switch (check.kind) {
  65. case 'min':
  66. (0, errorMessages_1.setResponseValueAndErrors)(res, 'minLength', typeof res.minLength === 'number' ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
  67. break;
  68. case 'max':
  69. (0, errorMessages_1.setResponseValueAndErrors)(res, 'maxLength', typeof res.maxLength === 'number' ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
  70. break;
  71. case 'email':
  72. switch (refs.emailStrategy) {
  73. case 'format:email':
  74. addFormat(res, 'email', check.message, refs);
  75. break;
  76. case 'format:idn-email':
  77. addFormat(res, 'idn-email', check.message, refs);
  78. break;
  79. case 'pattern:zod':
  80. addPattern(res, exports.zodPatterns.email, check.message, refs);
  81. break;
  82. }
  83. break;
  84. case 'url':
  85. addFormat(res, 'uri', check.message, refs);
  86. break;
  87. case 'uuid':
  88. addFormat(res, 'uuid', check.message, refs);
  89. break;
  90. case 'regex':
  91. addPattern(res, check.regex, check.message, refs);
  92. break;
  93. case 'cuid':
  94. addPattern(res, exports.zodPatterns.cuid, check.message, refs);
  95. break;
  96. case 'cuid2':
  97. addPattern(res, exports.zodPatterns.cuid2, check.message, refs);
  98. break;
  99. case 'startsWith':
  100. addPattern(res, RegExp(`^${processPattern(check.value)}`), check.message, refs);
  101. break;
  102. case 'endsWith':
  103. addPattern(res, RegExp(`${processPattern(check.value)}$`), check.message, refs);
  104. break;
  105. case 'datetime':
  106. addFormat(res, 'date-time', check.message, refs);
  107. break;
  108. case 'date':
  109. addFormat(res, 'date', check.message, refs);
  110. break;
  111. case 'time':
  112. addFormat(res, 'time', check.message, refs);
  113. break;
  114. case 'duration':
  115. addFormat(res, 'duration', check.message, refs);
  116. break;
  117. case 'length':
  118. (0, errorMessages_1.setResponseValueAndErrors)(res, 'minLength', typeof res.minLength === 'number' ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
  119. (0, errorMessages_1.setResponseValueAndErrors)(res, 'maxLength', typeof res.maxLength === 'number' ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
  120. break;
  121. case 'includes': {
  122. addPattern(res, RegExp(processPattern(check.value)), check.message, refs);
  123. break;
  124. }
  125. case 'ip': {
  126. if (check.version !== 'v6') {
  127. addFormat(res, 'ipv4', check.message, refs);
  128. }
  129. if (check.version !== 'v4') {
  130. addFormat(res, 'ipv6', check.message, refs);
  131. }
  132. break;
  133. }
  134. case 'emoji':
  135. addPattern(res, exports.zodPatterns.emoji, check.message, refs);
  136. break;
  137. case 'ulid': {
  138. addPattern(res, exports.zodPatterns.ulid, check.message, refs);
  139. break;
  140. }
  141. case 'base64': {
  142. switch (refs.base64Strategy) {
  143. case 'format:binary': {
  144. addFormat(res, 'binary', check.message, refs);
  145. break;
  146. }
  147. case 'contentEncoding:base64': {
  148. (0, errorMessages_1.setResponseValueAndErrors)(res, 'contentEncoding', 'base64', check.message, refs);
  149. break;
  150. }
  151. case 'pattern:zod': {
  152. addPattern(res, exports.zodPatterns.base64, check.message, refs);
  153. break;
  154. }
  155. }
  156. break;
  157. }
  158. case 'nanoid': {
  159. addPattern(res, exports.zodPatterns.nanoid, check.message, refs);
  160. }
  161. case 'toLowerCase':
  162. case 'toUpperCase':
  163. case 'trim':
  164. break;
  165. default:
  166. ((_) => { })(check);
  167. }
  168. }
  169. }
  170. return res;
  171. }
  172. exports.parseStringDef = parseStringDef;
  173. const escapeNonAlphaNumeric = (value) => Array.from(value)
  174. .map((c) => (/[a-zA-Z0-9]/.test(c) ? c : `\\${c}`))
  175. .join('');
  176. const addFormat = (schema, value, message, refs) => {
  177. if (schema.format || schema.anyOf?.some((x) => x.format)) {
  178. if (!schema.anyOf) {
  179. schema.anyOf = [];
  180. }
  181. if (schema.format) {
  182. schema.anyOf.push({
  183. format: schema.format,
  184. ...(schema.errorMessage &&
  185. refs.errorMessages && {
  186. errorMessage: { format: schema.errorMessage.format },
  187. }),
  188. });
  189. delete schema.format;
  190. if (schema.errorMessage) {
  191. delete schema.errorMessage.format;
  192. if (Object.keys(schema.errorMessage).length === 0) {
  193. delete schema.errorMessage;
  194. }
  195. }
  196. }
  197. schema.anyOf.push({
  198. format: value,
  199. ...(message && refs.errorMessages && { errorMessage: { format: message } }),
  200. });
  201. }
  202. else {
  203. (0, errorMessages_1.setResponseValueAndErrors)(schema, 'format', value, message, refs);
  204. }
  205. };
  206. const addPattern = (schema, regex, message, refs) => {
  207. if (schema.pattern || schema.allOf?.some((x) => x.pattern)) {
  208. if (!schema.allOf) {
  209. schema.allOf = [];
  210. }
  211. if (schema.pattern) {
  212. schema.allOf.push({
  213. pattern: schema.pattern,
  214. ...(schema.errorMessage &&
  215. refs.errorMessages && {
  216. errorMessage: { pattern: schema.errorMessage.pattern },
  217. }),
  218. });
  219. delete schema.pattern;
  220. if (schema.errorMessage) {
  221. delete schema.errorMessage.pattern;
  222. if (Object.keys(schema.errorMessage).length === 0) {
  223. delete schema.errorMessage;
  224. }
  225. }
  226. }
  227. schema.allOf.push({
  228. pattern: processRegExp(regex, refs),
  229. ...(message && refs.errorMessages && { errorMessage: { pattern: message } }),
  230. });
  231. }
  232. else {
  233. (0, errorMessages_1.setResponseValueAndErrors)(schema, 'pattern', processRegExp(regex, refs), message, refs);
  234. }
  235. };
  236. // Mutate z.string.regex() in a best attempt to accommodate for regex flags when applyRegexFlags is true
  237. const processRegExp = (regexOrFunction, refs) => {
  238. const regex = typeof regexOrFunction === 'function' ? regexOrFunction() : regexOrFunction;
  239. if (!refs.applyRegexFlags || !regex.flags)
  240. return regex.source;
  241. // Currently handled flags
  242. const flags = {
  243. i: regex.flags.includes('i'),
  244. m: regex.flags.includes('m'),
  245. s: regex.flags.includes('s'), // `.` matches newlines
  246. };
  247. // The general principle here is to step through each character, one at a time, applying mutations as flags require. We keep track when the current character is escaped, and when it's inside a group /like [this]/ or (also) a range like /[a-z]/. The following is fairly brittle imperative code; edit at your peril!
  248. const source = flags.i ? regex.source.toLowerCase() : regex.source;
  249. let pattern = '';
  250. let isEscaped = false;
  251. let inCharGroup = false;
  252. let inCharRange = false;
  253. for (let i = 0; i < source.length; i++) {
  254. if (isEscaped) {
  255. pattern += source[i];
  256. isEscaped = false;
  257. continue;
  258. }
  259. if (flags.i) {
  260. if (inCharGroup) {
  261. if (source[i].match(/[a-z]/)) {
  262. if (inCharRange) {
  263. pattern += source[i];
  264. pattern += `${source[i - 2]}-${source[i]}`.toUpperCase();
  265. inCharRange = false;
  266. }
  267. else if (source[i + 1] === '-' && source[i + 2]?.match(/[a-z]/)) {
  268. pattern += source[i];
  269. inCharRange = true;
  270. }
  271. else {
  272. pattern += `${source[i]}${source[i].toUpperCase()}`;
  273. }
  274. continue;
  275. }
  276. }
  277. else if (source[i].match(/[a-z]/)) {
  278. pattern += `[${source[i]}${source[i].toUpperCase()}]`;
  279. continue;
  280. }
  281. }
  282. if (flags.m) {
  283. if (source[i] === '^') {
  284. pattern += `(^|(?<=[\r\n]))`;
  285. continue;
  286. }
  287. else if (source[i] === '$') {
  288. pattern += `($|(?=[\r\n]))`;
  289. continue;
  290. }
  291. }
  292. if (flags.s && source[i] === '.') {
  293. pattern += inCharGroup ? `${source[i]}\r\n` : `[${source[i]}\r\n]`;
  294. continue;
  295. }
  296. pattern += source[i];
  297. if (source[i] === '\\') {
  298. isEscaped = true;
  299. }
  300. else if (inCharGroup && source[i] === ']') {
  301. inCharGroup = false;
  302. }
  303. else if (!inCharGroup && source[i] === '[') {
  304. inCharGroup = true;
  305. }
  306. }
  307. try {
  308. const regexTest = new RegExp(pattern);
  309. }
  310. catch {
  311. console.warn(`Could not convert regex pattern at ${refs.currentPath.join('/')} to a flag-independent form! Falling back to the flag-ignorant source`);
  312. return regex.source;
  313. }
  314. return pattern;
  315. };
  316. //# sourceMappingURL=string.js.map