diff --git a/opapi/package.json b/opapi/package.json index 348e134b..4ac0cc56 100644 --- a/opapi/package.json +++ b/opapi/package.json @@ -1,6 +1,6 @@ { "name": "@bpinternal/opapi", - "version": "0.10.19", + "version": "0.10.20", "description": "", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -45,6 +45,7 @@ "decompress": "4.2.1", "execa": "8.0.1", "json-schema-to-typescript": "13.1.2", + "json-schema-to-zod": "1.1.1", "lodash": "^4.17.21", "openapi-typescript": "6.7.6", "openapi3-ts": "2.0.2", diff --git a/opapi/pnpm-lock.yaml b/opapi/pnpm-lock.yaml index aeab4872..7a7f0e4f 100644 --- a/opapi/pnpm-lock.yaml +++ b/opapi/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: json-schema-to-typescript: specifier: 13.1.2 version: 13.1.2 + json-schema-to-zod: + specifier: 1.1.1 + version: 1.1.1 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -773,6 +776,9 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -780,6 +786,9 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} @@ -841,6 +850,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} @@ -904,9 +917,15 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -956,10 +975,17 @@ packages: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -978,6 +1004,14 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es5-ext@0.10.62: resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} engines: {node: '>=0.10'} @@ -1005,6 +1039,11 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1033,6 +1072,9 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} @@ -1074,6 +1116,9 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + formidable@2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -1085,9 +1130,16 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + get-stdin@8.0.0: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} engines: {node: '>=10'} @@ -1122,12 +1174,18 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + graphlib@2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1136,6 +1194,25 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hexoid@1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -1211,15 +1288,28 @@ packages: js-tokens@8.0.3: resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + json-refs@3.0.15: + resolution: {integrity: sha512-0vOQd9eLNBL18EGl5yYaO44GhixmImes2wiYn9Z3sag3QnehWrYWlB9AFtMxCL2Bj3fyxgDYkxGFEU/chlYssw==} + engines: {node: '>=0.8'} + hasBin: true + json-schema-to-typescript@13.1.2: resolution: {integrity: sha512-17G+mjx4nunvOpkPvcz7fdwUwYCEwyH8vR3Ym3rFiQ8uzAL3go+c1306Kk7iGRk8HuXBXqy+JJJmpYl0cvOllw==} engines: {node: '>=12.0.0'} hasBin: true + json-schema-to-zod@1.1.1: + resolution: {integrity: sha512-YhU/zVhEMUBQE5/tOWPEcajdjEIpWFHVitQz0A7knERlzldw705dhImXiwgWKvkivU4GkF6C1efREzAFDKXvmQ==} + hasBin: true + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -1297,6 +1387,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -1309,6 +1403,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1345,6 +1444,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + native-promise-only@0.8.1: + resolution: {integrity: sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==} + next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} @@ -1364,6 +1466,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1404,6 +1510,9 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-loader@1.0.12: + resolution: {integrity: sha512-n7oDG8B+k/p818uweWrOixY9/Dsr89o2TkCm6tOTex3fpdo2+BFDgR+KpB37mGKBRsBAlR8CIJMFN0OEy/7hIQ==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1487,6 +1596,10 @@ packages: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + qs@6.12.3: + resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1545,6 +1658,15 @@ packages: resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} hasBin: true + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1553,6 +1675,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1578,6 +1704,9 @@ packages: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -1616,6 +1745,11 @@ packages: engines: {node: '>=8'} hasBin: true + superagent@7.1.6: + resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} + engines: {node: '>=6.4.0 <13 || >=14'} + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2369,10 +2503,16 @@ snapshots: arg@4.1.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} array-union@2.1.0: {} + asap@2.0.6: {} + assert-plus@1.0.0: {} assertion-error@1.1.0: {} @@ -2432,6 +2572,14 @@ snapshots: cac@6.7.14: {} + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + call-me-maybe@1.0.2: {} chai@4.4.1: @@ -2516,8 +2664,12 @@ snapshots: commander@4.1.1: {} + component-emitter@1.3.1: {} + concat-map@0.0.1: {} + cookiejar@2.1.4: {} + core-util-is@1.0.2: {} core-util-is@1.0.3: {} @@ -2581,8 +2733,19 @@ snapshots: dependencies: type-detect: 4.0.8 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + delayed-stream@1.0.0: {} + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff-sequences@29.6.3: {} diff@4.0.2: {} @@ -2597,6 +2760,12 @@ snapshots: dependencies: once: 1.4.0 + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + es5-ext@0.10.62: dependencies: es6-iterator: 2.0.3 @@ -2675,6 +2844,8 @@ snapshots: escape-string-regexp@1.0.5: {} + esprima@4.0.1: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.5 @@ -2724,6 +2895,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.5 + fast-safe-stringify@2.1.1: {} + fastq@1.15.0: dependencies: reusify: 1.0.4 @@ -2754,6 +2927,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + formidable@2.1.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.12.3 + fs-constants@1.0.0: {} fs.realpath@1.0.0: {} @@ -2761,8 +2941,18 @@ snapshots: fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + get-func-name@2.0.2: {} + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + get-stdin@8.0.0: {} get-stream@2.3.1: @@ -2801,14 +2991,36 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + graceful-fs@4.2.11: {} grapheme-splitter@1.0.4: {} + graphlib@2.1.8: + dependencies: + lodash: 4.17.21 + has-flag@3.0.0: {} has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hexoid@1.0.0: {} + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -2858,10 +3070,28 @@ snapshots: js-tokens@8.0.3: {} + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + json-refs@3.0.15: + dependencies: + commander: 4.1.1 + graphlib: 2.1.8 + js-yaml: 3.14.1 + lodash: 4.17.21 + native-promise-only: 0.8.1 + path-loader: 1.0.12 + slash: 3.0.0 + uri-js: 4.4.1 + transitivePeerDependencies: + - supports-color + json-schema-to-typescript@13.1.2: dependencies: '@bcherny/json-schema-ref-parser': 10.0.5-fork @@ -2879,6 +3109,14 @@ snapshots: mz: 2.7.0 prettier: 2.8.8 + json-schema-to-zod@1.1.1: + dependencies: + '@types/json-schema': 7.0.15 + json-refs: 3.0.15 + prettier: 2.8.8 + transitivePeerDependencies: + - supports-color + json-schema-traverse@1.0.0: {} json-to-ast@2.1.0: @@ -2953,6 +3191,8 @@ snapshots: merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.5: dependencies: braces: 3.0.2 @@ -2964,6 +3204,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mime@2.6.0: {} + mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -2995,6 +3237,8 @@ snapshots: nanoid@3.3.7: {} + native-promise-only@0.8.1: {} + next-tick@1.1.0: {} normalize-path@3.0.0: {} @@ -3009,6 +3253,8 @@ snapshots: object-assign@4.1.1: {} + object-inspect@1.13.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3050,6 +3296,13 @@ snapshots: path-key@4.0.0: {} + path-loader@1.0.12: + dependencies: + native-promise-only: 0.8.1 + superagent: 7.1.6 + transitivePeerDependencies: + - supports-color + path-type@4.0.0: {} pathe@1.1.2: {} @@ -3110,6 +3363,10 @@ snapshots: punycode@2.3.0: {} + qs@6.12.3: + dependencies: + side-channel: 1.0.6 + queue-microtask@1.2.3: {} radash@12.1.0: {} @@ -3179,12 +3436,30 @@ snapshots: dependencies: commander: 2.20.3 + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -3203,6 +3478,8 @@ snapshots: dependencies: whatwg-url: 7.1.0 + sprintf-js@1.0.3: {} + stack-trace@0.0.10: {} stackback@0.0.2: {} @@ -3240,6 +3517,22 @@ snapshots: pirates: 4.0.5 ts-interface-checker: 0.1.13 + superagent@7.1.6: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.4 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.12.3 + readable-stream: 3.6.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + supports-color@5.5.0: dependencies: has-flag: 3.0.0 diff --git a/opapi/src/handler-generator/export-schemas.ts b/opapi/src/handler-generator/export-schemas.ts index 0f227707..2e0e70d8 100644 --- a/opapi/src/handler-generator/export-schemas.ts +++ b/opapi/src/handler-generator/export-schemas.ts @@ -4,14 +4,22 @@ import * as jsonschema from '../jsonschema' import * as utils from './utils' import pathlib from 'path' import fs from 'fs/promises' +import { jsonSchemaToZod } from 'json-schema-to-zod' +import { OpenApiZodAny } from '@anatine/zod-openapi' +type jsonSchemaToZodInput = Parameters[0] +type jsonSchemaToTsInput = Parameters[0] type Module = { name: string; filename: string } +type ExportSchemasOptions = { + includeJsonSchemas: boolean + includeZodSchemas: boolean + includeTypes: boolean +} -const toTs = async (originalSchema: JSONSchema7, name: string): Promise => { +const jsonSchemaToTs = async (originalSchema: JSONSchema7, name: string): Promise => { let { title, ...schema } = originalSchema schema = jsonschema.setDefaultAdditionalProperties(schema, false) - type jsonSchemaToTsInput = Parameters[0] const typeCode = await compile(schema as jsonSchemaToTsInput, name, { unknownAny: false, bannerComment: '', @@ -22,48 +30,112 @@ const toTs = async (originalSchema: JSONSchema7, name: string): Promise return `${typeCode}\n` } -export const exportSchemas = (schemas: Record) => async (outDir: string) => { - await fs.mkdir(outDir, { recursive: true }) +const zodToJsonSchema = (zodSchema: OpenApiZodAny): JSONSchema7 => { + let jsonSchema = jsonschema.generateSchemaFromZod(zodSchema, { allowUnions: true }) as JSONSchema7 + jsonSchema = jsonschema.replaceNullableWithUnion(jsonSchema) + jsonSchema = jsonschema.replaceOneOfWithAnyOf(jsonSchema) + return jsonSchema +} + +const DEFAULT_OPTIONS: ExportSchemasOptions = { + includeJsonSchemas: true, + includeZodSchemas: true, + includeTypes: true, +} + +/** + * export any record of json schema to: + * - json schemas + * - zod schemas + * - typescript types + * + * allows fully separating build time schemas from the ones used at runtime + */ +export const exportJsonSchemas = + (schemas: Record) => + async (outDir: string, opts: Partial = {}) => { + const options = { ...DEFAULT_OPTIONS, ...opts } + await fs.mkdir(outDir, { recursive: true }) - const jsonFiles: Module[] = [] - const typeFiles: Module[] = [] + const jsonFiles: Module[] = [] + const zodFiles: Module[] = [] + const typeFiles: Module[] = [] - for (const [name, schema] of Object.entries(schemas)) { - const jsonSchema = jsonschema.replaceNullableWithUnion(schema) + for (const [name, schema] of Object.entries(schemas)) { + const jsonSchema = jsonschema.replaceNullableWithUnion(schema) - // json file - const jsonFileName = `${name}.j` - const jsonCode = [ - "import type { JSONSchema7 } from 'json-schema'", - `const schema: JSONSchema7 = ${JSON.stringify(jsonSchema, null, 2)}`, - `export default schema`, + // json file + if (options.includeJsonSchemas) { + const jsonFileName = `${name}.j` + const jsonCode = [ + "import type { JSONSchema7 } from 'json-schema'", + `const schema: JSONSchema7 = ${JSON.stringify(jsonSchema, null, 2)}`, + `export default schema`, + ].join('\n') + const jsonFilePath = pathlib.join(outDir, `${jsonFileName}.ts`) + await fs.writeFile(jsonFilePath, jsonCode) + jsonFiles.push({ name, filename: jsonFileName }) + } + + // zod file + if (options.includeZodSchemas) { + const zodFileName = `${name}.z` + const zodCode = jsonSchemaToZod(jsonSchema as jsonSchemaToZodInput).replace(/\.catchall\(z\.never\(\)\)/g, '') + const zodFilePath = pathlib.join(outDir, `${zodFileName}.ts`) + await fs.writeFile(zodFilePath, zodCode) + zodFiles.push({ name, filename: zodFileName }) + } + + // type file + if (options.includeTypes) { + const typeFileName = `${name}.t` + const typeCode = await jsonSchemaToTs(jsonSchema, name) + const typeFilePath = pathlib.join(outDir, `${typeFileName}.ts`) + await fs.writeFile(typeFilePath, typeCode) + typeFiles.push({ name, filename: typeFileName }) + } + } + + // index file + const indexCode = [ + ...jsonFiles.map(({ name, filename }) => `import json_${name} from './${filename}'`), + ...zodFiles.map(({ name, filename }) => `import zod_${name} from './${filename}'`), + ...typeFiles.map(({ name, filename }) => `import type { ${utils.pascalCase(name)} } from './${filename}'`), + '', + `export const json = {`, + ...jsonFiles.map(({ name }) => ` ${name}: json_${name},`), + `}`, + '', + `export const zod = {`, + ...zodFiles.map(({ name }) => ` ${name}: zod_${name},`), + `}`, + '', + `export type Types = {`, + ...typeFiles.map(({ name }) => ` ${name}: ${utils.pascalCase(name)}`), + `}`, ].join('\n') - const jsonFilePath = pathlib.join(outDir, `${jsonFileName}.ts`) - await fs.writeFile(jsonFilePath, jsonCode) - jsonFiles.push({ name, filename: jsonFileName }) - // type file - const typeFileName = `${name}.t` - const typeCode = await toTs(jsonSchema, name) - const typeFilePath = pathlib.join(outDir, `${typeFileName}.ts`) - await fs.writeFile(typeFilePath, typeCode) - typeFiles.push({ name, filename: typeFileName }) + const indexPath = pathlib.join(outDir, 'index.ts') + await fs.writeFile(indexPath, indexCode) } - // index file - const indexCode = [ - ...jsonFiles.map(({ name, filename }) => `import json_${name} from './${filename}'`), - ...typeFiles.map(({ name, filename }) => `import type { ${utils.pascalCase(name)} } from './${filename}'`), - '', - `export const json = {`, - ...jsonFiles.map(({ name }) => ` ${name}: json_${name},`), - `}`, - '', - `export type Types = {`, - ...typeFiles.map(({ name }) => ` ${name}: ${utils.pascalCase(name)}`), - `}`, - ].join('\n') - - const indexPath = pathlib.join(outDir, 'index.ts') - await fs.writeFile(indexPath, indexCode) +/** + * export any record of zod schema to: + * - json schemas + * - zod schemas + * - typescript types + * + * allows fully separating build time schemas from the ones used at runtime + */ +export const exportZodSchemas = (schemas: Record) => { + const jsonSchemas = Object.entries(schemas).reduce( + (acc, [name, zodSchema]) => { + return { + ...acc, + [name]: zodToJsonSchema(zodSchema), + } + }, + {} as Record, + ) + return exportJsonSchemas(jsonSchemas) } diff --git a/opapi/src/handler-generator/index.ts b/opapi/src/handler-generator/index.ts index 55cebaa0..72625f59 100644 --- a/opapi/src/handler-generator/index.ts +++ b/opapi/src/handler-generator/index.ts @@ -6,7 +6,7 @@ import { toRequestSchema, toResponseSchema } from './map-operation' import { exportErrors } from './export-errors' import { exportTypings } from './export-typings' import { exportRouteTree } from './export-tree' -import { exportSchemas } from './export-schemas' +import { exportJsonSchemas } from './export-schemas' import { JSONSchema7 } from 'json-schema' import { exportHandler } from './export-handler' import { generateOpenapi } from '../generator' @@ -15,7 +15,7 @@ type JsonSchemaMap = Record type ExportableSchema = { exportSchemas: (outDir: string) => Promise } const toExportableSchema = (schemas: JsonSchemaMap): ExportableSchema => ({ - exportSchemas: (outDir: string) => exportSchemas(schemas)(outDir), + exportSchemas: (outDir: string) => exportJsonSchemas(schemas)(outDir, { includeZodSchemas: false }), }) export const generateHandler = async ( diff --git a/opapi/src/jsonschema.test.ts b/opapi/src/jsonschema.test.ts index f78f02d8..30f371d2 100644 --- a/opapi/src/jsonschema.test.ts +++ b/opapi/src/jsonschema.test.ts @@ -1,6 +1,12 @@ import { JSONSchema7 } from 'json-schema' import { test, expect } from 'vitest' -import { JsonSchema, NullableJsonSchema, replaceNullableWithUnion, setDefaultAdditionalProperties } from './jsonschema' +import { + JsonSchema, + NullableJsonSchema, + replaceNullableWithUnion, + replaceOneOfWithAnyOf, + setDefaultAdditionalProperties, +} from './jsonschema' import { jsonSchemaBuilder, JsonSchemaBuilder } from './handler-generator/utils' import _ from 'lodash' @@ -176,3 +182,45 @@ test('setDefaultAdditionalProperties with real example', () => { const actual = setDefaultAdditionalProperties(input, false) expect(actual).toEqual(expected) }) + +test('replaceOneOfWithAnyOf', () => { + const input: JsonSchema = { + type: 'object', + properties: { + id: { + oneOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }, + }, + required: ['id'], + additionalProperties: false, + } + + const actual = replaceOneOfWithAnyOf(input) + + const expected: JsonSchema = { + type: 'object', + properties: { + id: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }, + }, + required: ['id'], + additionalProperties: false, + } + + expect(actual).toEqual(expected) +}) diff --git a/opapi/src/jsonschema.ts b/opapi/src/jsonschema.ts index 5b6c2a7c..e6153049 100644 --- a/opapi/src/jsonschema.ts +++ b/opapi/src/jsonschema.ts @@ -1,5 +1,5 @@ import { OpenApiZodAny, generateSchema as generateJsonSchema } from '@anatine/zod-openapi' -import { JSONSchema7 } from 'json-schema' +import { JSONSchema7, JSONSchema7Definition } from 'json-schema' import type { SchemaObject } from 'openapi3-ts' import { removeFromArray } from './util' import _ from 'lodash' @@ -69,39 +69,100 @@ export function schemaIsEmptyObject(schema: SchemaObject) { return false } +const exploreJsonSchemaDef = + (cb: (s: JsonSchema) => JsonSchema) => + (inputSchema: JSONSchema7Definition): JSONSchema7Definition => { + if (typeof inputSchema === 'boolean') { + return inputSchema + } + return exploreJsonSchema(cb)(inputSchema) + } + +export const exploreJsonSchema = + (cb: (s: JsonSchema) => JsonSchema) => + (inputSchema: JsonSchema): JsonSchema => { + const mappedSchema = cb(inputSchema) + + if (mappedSchema.type === 'object') { + const properties = mappedSchema.properties + ? _.mapValues(mappedSchema.properties, exploreJsonSchema(cb)) + : mappedSchema.properties + const additionalProperties = + typeof mappedSchema.additionalProperties === 'object' + ? exploreJsonSchema(cb)(mappedSchema.additionalProperties) + : mappedSchema.additionalProperties + return { ...mappedSchema, properties, additionalProperties } + } + + if (mappedSchema.type === 'array') { + if (mappedSchema.items === undefined) { + return mappedSchema + } + if (Array.isArray(mappedSchema.items)) { + return { + ...mappedSchema, + items: mappedSchema.items.map(exploreJsonSchemaDef(cb)), + } + } + return { ...mappedSchema, items: exploreJsonSchemaDef(cb)(mappedSchema.items) } + } + + if (mappedSchema.anyOf) { + return { + ...mappedSchema, + anyOf: mappedSchema.anyOf.map(exploreJsonSchemaDef(cb)), + } + } + + if (mappedSchema.allOf) { + return { + ...mappedSchema, + allOf: mappedSchema.allOf.map(exploreJsonSchemaDef(cb)), + } + } + + if (mappedSchema.oneOf) { + return { + ...mappedSchema, + oneOf: mappedSchema.oneOf.map(exploreJsonSchemaDef(cb)), + } + } + + return mappedSchema + } + /** * Lib "@anatine/zod-openapi" transforms zod to json-schema using the nullable property. * This property is not officially supported by json-schema, but supported by ajv (see: https://ajv.js.org/json-schema.html#nullable) * Since it's not officially supported, some tools like "json-schema-to-typescript" don't support it. * This function replaces all occurences of { type: T, nullable: true } with { anyOf: [{ type: T }, { type: 'null' }] } */ -export const replaceNullableWithUnion = (nullableSchema: NullableJsonSchema): JSONSchema7 => { - const { nullable, ...schema } = nullableSchema - if (nullable) { - const { title, description, ...rest } = replaceNullableWithUnion(schema) - return { title, description, anyOf: [rest, { type: 'null' }] } - } - - if (schema.type === 'object') { - const properties = schema.properties ? _.mapValues(schema.properties, replaceNullableWithUnion) : schema.properties - const additionalProperties = - typeof schema.additionalProperties === 'object' - ? replaceNullableWithUnion(schema.additionalProperties) - : schema.additionalProperties - return { ...schema, properties, additionalProperties } - } - - if (schema.type === 'array') { - if (schema.items === undefined) { - return schema +export const replaceNullableWithUnion = (schema: NullableJsonSchema): JSONSchema7 => { + const mapper = exploreJsonSchema((s) => { + const { nullable, ...schema } = s as NullableJsonSchema + if (nullable) { + const { title, description, ...rest } = schema + return { title, description, anyOf: [rest, { type: 'null' }] } } - if (Array.isArray(schema.items)) { - return { ...schema, items: schema.items.map((s) => replaceNullableWithUnion(s as NullableJsonSchema)) } - } - return { ...schema, items: replaceNullableWithUnion(schema.items as NullableJsonSchema) } - } + return schema + }) + return mapper(schema) +} - return schema +/** + * Lib "@anatine/zod-openapi" transforms zod unions to json-schema oneOf. + * This is a mistake as a union does not enforce that only one of the types is present. + * This function replaces all occurences of { oneOf: [{ type: T1 }, { type: T2 }] } with { anyOf: [{ type: T1 }, { type: T2 }] } + */ +export const replaceOneOfWithAnyOf = (oneOfSchema: JsonSchema): JSONSchema7 => { + const mapper = exploreJsonSchema((schema) => { + if (schema.oneOf) { + const { oneOf, ...rest } = schema + return { anyOf: oneOf, ...rest } + } + return schema + }) + return mapper(oneOfSchema) } const _setDefaultAdditionalPropertiesInPlace = (schema: JsonSchema, additionalProperties: boolean): void => { diff --git a/opapi/src/opapi.ts b/opapi/src/opapi.ts index 765f1908..c098d1a3 100644 --- a/opapi/src/opapi.ts +++ b/opapi/src/opapi.ts @@ -129,3 +129,5 @@ export type ParameterOf> = export type SectionOf> = O extends OpenApi ? Sexion : never + +export { exportJsonSchemas, exportZodSchemas } from './handler-generator/export-schemas' diff --git a/opapi/test/export-schemas.test.ts b/opapi/test/export-schemas.test.ts new file mode 100644 index 00000000..27c132df --- /dev/null +++ b/opapi/test/export-schemas.test.ts @@ -0,0 +1,85 @@ +import fs from 'fs' +import { describe, expect, it } from 'vitest' +import { join, basename } from 'path' +import { exportJsonSchemas, exportZodSchemas } from '../src' +import { validateTypescriptFile } from './util' +import { getFiles } from '../src/file' +import { z } from 'zod' + +const schemaFiles = ['index.ts', 'user.j.ts', 'user.z.ts', 'user.t.ts', 'ticket.j.ts', 'ticket.z.ts', 'ticket.t.ts'] + +const assert = async (genFolder: string, exporter: (outDir: string) => Promise) => { + await exporter(genFolder) + + schemaFiles.forEach((file) => { + const filename = join(genFolder, file) + expect(fs.existsSync(filename), `${filename} should exist`).toBe(true) + + if (filename.endsWith('.ts')) { + validateTypescriptFile(filename) + } + + if (filename.endsWith('.z.ts')) { + const fileContent = fs.readFileSync(filename, 'utf-8') + expect(fileContent).not.toContain('.any()') // there's no reason to use .any() in the provided schemas + } + }) + + const files = new Set(getFiles(genFolder).map((f) => basename(f))) + expect(files).toEqual(new Set(schemaFiles)) +} + +describe('schemas generator', () => { + it('should be able to export arbitrary json schemas', async () => { + const genFolder = join(__dirname, 'gen/json-schemas') + await assert( + genFolder, + exportJsonSchemas({ + user: { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + id: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }, + }, + required: ['name'], + }, + ticket: { + type: 'object', + properties: { + title: { type: 'string' }, + content: { type: 'string' }, + }, + required: ['title'], + }, + }), + ) + }) + + it('should be able to export arbitrary zod schemas', async () => { + const genFolder = join(__dirname, 'gen/zod-schemas') + await assert( + genFolder, + exportZodSchemas({ + user: z.object({ + name: z.string(), + age: z.number().optional(), + id: z.union([z.string(), z.number()]), + }), + ticket: z.object({ + title: z.string(), + content: z.string().optional(), + }), + }), + ) + }) +})