diff --git a/common/changes/@subsquid/graphql-server/fix-SDKTL-1_class_validator_2025-05-28-07-46.json b/common/changes/@subsquid/graphql-server/fix-SDKTL-1_class_validator_2025-05-28-07-46.json new file mode 100644 index 000000000..40b2efc18 --- /dev/null +++ b/common/changes/@subsquid/graphql-server/fix-SDKTL-1_class_validator_2025-05-28-07-46.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@subsquid/graphql-server", + "comment": "override validate function to make it compatible with the latest class-validator package", + "type": "minor" + } + ], + "packageName": "@subsquid/graphql-server" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 9fc8bb496..9fa5a84c8 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -2976,8 +2976,8 @@ packages: resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} dev: false - /@types/validator@13.11.2: - resolution: {integrity: sha512-nIKVVQKT6kGKysnNt+xLobr+pFJNssJRi2s034wgWeFBUx01fI8BeHTW2TcRp7VcFu9QCYG8IlChTuovcm0oKQ==} + /@types/validator@13.15.1: + resolution: {integrity: sha512-9gG6ogYcoI2mCMLdcO0NYI0AYrbxIjv0MDmy/5Ywo6CpWWrqYayc+mmgxRsCgtcGJm9BSbXkMsmxGah1iGHAAQ==} dev: false /@types/websocket@1.0.10: @@ -3654,11 +3654,11 @@ packages: engines: {node: '>=8'} dev: false - /class-validator@0.14.0: - resolution: {integrity: sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==} + /class-validator@0.14.2: + resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} dependencies: - '@types/validator': 13.11.2 - libphonenumber-js: 1.10.45 + '@types/validator': 13.15.1 + libphonenumber-js: 1.12.8 validator: 13.11.0 dev: false @@ -5058,8 +5058,8 @@ packages: package-json: 6.5.0 dev: false - /libphonenumber-js@1.10.45: - resolution: {integrity: sha512-eeHcvGafEYCaKB4fo2uBINfG7j7PcGwBHUaTVfbwl/6KcjCgIKNlIOsSXVRp9BH10NQwmvvk+nQ1e/Yp4BGB7w==} + /libphonenumber-js@1.12.8: + resolution: {integrity: sha512-f1KakiQJa9tdc7w1phC2ST+DyxWimy9c3g3yeF+84QtEanJr2K77wAmBPP22riU05xldniHsvXuflnLZ4oysqA==} dev: false /local-pkg@0.5.0: @@ -6309,7 +6309,7 @@ packages: engines: {node: '>=12.20'} dev: false - /type-graphql@1.2.0-rc.1(class-validator@0.14.0)(graphql@15.8.0): + /type-graphql@1.2.0-rc.1(class-validator@0.14.2)(graphql@15.8.0): resolution: {integrity: sha512-W1p51DN+n/zX4ilunMC6/FcyGlx/ND3hreQ0ARDhfhyR9oGtfKzQNnkHhk8uXlYm2zzyTEd1LkRHJr8bbnRlIA==} engines: {node: '>= 10.13'} requiresBuild: true @@ -6320,7 +6320,7 @@ packages: '@types/glob': 7.2.0 '@types/node': 18.19.0 '@types/semver': 7.5.6 - class-validator: 0.14.0 + class-validator: 0.14.2 glob: 7.2.0 graphql: 15.8.0 graphql-query-complexity: 0.7.2(graphql@15.8.0) @@ -6928,7 +6928,7 @@ packages: dev: false file:projects/astar-erc20.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-ku7TDQmmBSProxyt2R84RHNBi33LeqHdv7mt1OZ6ALJcb+4ob6mGoSCZr/br/Uu8ocL3Nft5SG+tqtWQQzNKtA==, tarball: file:projects/astar-erc20.tgz} + resolution: {integrity: sha512-h/Ui36RAsNWEr7GB5f8LZirIbM0xKPPnCEg+JzRHIfzx4d9ytFC5IqMD8YmXIdd2Jcu4jJSKlzl3k9RLyag9gQ==, tarball: file:projects/astar-erc20.tgz} id: file:projects/astar-erc20.tgz name: '@rush-temp/astar-erc20' version: 0.0.0 @@ -6962,7 +6962,7 @@ packages: dev: false file:projects/balances.tgz(supports-color@8.1.1): - resolution: {integrity: sha512-tl+/Pm60lXbxitrIDsuwv1FN4ceFkvylMB/r8gJ9+t7eJB7/23UcuGtqk9xOZN8f6tc7fXLATo1P8R16kxDNpA==, tarball: file:projects/balances.tgz} + resolution: {integrity: sha512-pVbvU9TudRnslECyftaoS2wfatI4QoSPsSW0vNXYp5G4OqbJgA5pwFiydqUQY9s8YlUscFOS72LYv4nSzUN2zA==, tarball: file:projects/balances.tgz} id: file:projects/balances.tgz name: '@rush-temp/balances' version: 0.0.0 @@ -7066,7 +7066,7 @@ packages: dev: false file:projects/erc20-transfers.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-OatnMfM4fVpTfhd+S/x6FEEjsoxSeURRESdnDhy8tiIBO3hRoZuZpnkZD0Hms1M0U6kZrWdVFZ6Mq4fLvyBU9w==, tarball: file:projects/erc20-transfers.tgz} + resolution: {integrity: sha512-MR3ZHLJ0v315saPxL1M01gdBzriD6pWwqRPCmFNXP6vL7MV1vYzUCBlcNKPIjGHMB03K+uBYUEJSgWA8FrOHwQ==, tarball: file:projects/erc20-transfers.tgz} id: file:projects/erc20-transfers.tgz name: '@rush-temp/erc20-transfers' version: 0.0.0 @@ -7304,7 +7304,7 @@ packages: dev: false file:projects/graphql-server.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-OZrDqN92VnqBCdQ4Sx8MCuzi1pBC0x8PbZ/YSmsQAKonKhzTc4wRpfWs05PnaIry3mZKYfxVaZ3AfbeF9DnCqQ==, tarball: file:projects/graphql-server.tgz} + resolution: {integrity: sha512-pqShsaj39GLC5CV4ACSN+nxatgfmrL4XOqRwxR5XSudb89SE4ytKGyGpgju/smdzfUCmWJXQ3QSK78ypAGINzQ==, tarball: file:projects/graphql-server.tgz} id: file:projects/graphql-server.tgz name: '@rush-temp/graphql-server' version: 0.0.0 @@ -7325,7 +7325,7 @@ packages: apollo-server-core: 3.13.0(graphql@15.8.0) apollo-server-express: 3.13.0(express@4.18.2)(graphql@15.8.0) apollo-server-plugin-response-cache: 3.7.1(graphql@15.8.0) - class-validator: 0.14.0 + class-validator: 0.14.2 commander: 11.1.0 dotenv: 16.3.1 expect: 29.7.0 @@ -7335,7 +7335,7 @@ packages: keyv: 4.5.4 mocha: 10.7.3 pg: 8.11.3 - type-graphql: 1.2.0-rc.1(class-validator@0.14.0)(graphql@15.8.0) + type-graphql: 1.2.0-rc.1(class-validator@0.14.2)(graphql@15.8.0) typeorm: 0.3.17(pg@8.11.3)(supports-color@8.1.1)(ts-node@10.9.2) typescript: 5.5.4 ws: 8.14.2 @@ -7459,7 +7459,7 @@ packages: dev: false file:projects/polkavm-erc20.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-nuRkDZcbOtR1NvsuoRs1Tp+CiqOwRpGYS3PXlzKKNCvHQktkS1BwhGa2seaQKiGPNknecQMmvW7PX/SX7lEWUg==, tarball: file:projects/polkavm-erc20.tgz} + resolution: {integrity: sha512-T53CQjkU8OWGxdFPioy0K+vaIYcJCbYvTxrfwkEEwlvU2vfmTmoNwg0kTwFfM8n+Zzlj+KMSJ0SR7iXvFBiA0A==, tarball: file:projects/polkavm-erc20.tgz} id: file:projects/polkavm-erc20.tgz name: '@rush-temp/polkavm-erc20' version: 0.0.0 @@ -7529,7 +7529,7 @@ packages: dev: false file:projects/shibuya-erc20.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-464GXdr8oKXtgAdZZblyvKmByd9/elprE24VThtrkYbw9tj+4fbgxmHrpxh7GWCSSKF/FhnyddKCpFEy3OYVKQ==, tarball: file:projects/shibuya-erc20.tgz} + resolution: {integrity: sha512-JyaZWYTqvzG9NlQ5rTZ6Ak+7omrrj4JOXee2by6WEXe1FK98XO+D8rsByUiP+cwWb68AcCCTUvG5TUM+giAOaA==, tarball: file:projects/shibuya-erc20.tgz} id: file:projects/shibuya-erc20.tgz name: '@rush-temp/shibuya-erc20' version: 0.0.0 @@ -7560,7 +7560,7 @@ packages: dev: false file:projects/shibuya-psp22.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-icuTvTepzTgjnmWSI9S8Wg1MMEgfEaAdkWSKVF0jY+AR0R4F3rvQKqJYo2QF7ay+Gm8DLTMOXylUZ3rIuOGFMQ==, tarball: file:projects/shibuya-psp22.tgz} + resolution: {integrity: sha512-UJrpXlNGwx1Hh8ncBWlKP80cXfsMJeZ1HpDN8vKlcFN6SZGpI/Yq8xLpfgSUU98RRC3AIOzWyYiBEMgITnW8Qg==, tarball: file:projects/shibuya-psp22.tgz} id: file:projects/shibuya-psp22.tgz name: '@rush-temp/shibuya-psp22' version: 0.0.0 @@ -7591,7 +7591,7 @@ packages: dev: false file:projects/solana-dump.tgz: - resolution: {integrity: sha512-RvUgzWVWKdyUgAWGYSGmFrlUkB+kGvQmUQFzjRg3/rRoXn5Nj72ywsusEIiSN0ByLddOwFxy61zT3K42OA2+8A==, tarball: file:projects/solana-dump.tgz} + resolution: {integrity: sha512-InkBLfLXopZDGZRGfJD1DB/+bZK0I0Ivqin/PMHSnRpd2x+S4Gzs5QppB2PgP0FSMn6a3irKeIuaiQh38kfEiA==, tarball: file:projects/solana-dump.tgz} name: '@rush-temp/solana-dump' version: 0.0.0 dependencies: @@ -7600,7 +7600,7 @@ packages: dev: false file:projects/solana-example.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-MWdgGyEXpNf+cfgVQlA2L2HGdFh6lXqu5qHoPajSZMkxLUFrdD5AcjV2aoc3Ifp8BV0VH6I/TcxHTtJjAVcw+g==, tarball: file:projects/solana-example.tgz} + resolution: {integrity: sha512-rtu+dsb/Xmox3DAP3sK+68fDbya/MGj9Gfdru1fENhnHOKOkvBrGzrr3ZIeB73/DIX8je0KccrxUtC+SozUrXg==, tarball: file:projects/solana-example.tgz} id: file:projects/solana-example.tgz name: '@rush-temp/solana-example' version: 0.0.0 @@ -7649,7 +7649,7 @@ packages: dev: false file:projects/solana-objects.tgz: - resolution: {integrity: sha512-nbxz2CxOvsW+clzaDb3PuyNQ6KsY4h9mBPaSS6pERxwy+1H8rjDmfMG1rMTTuKcQQcbStxuf7YiDcJRirRvuHA==, tarball: file:projects/solana-objects.tgz} + resolution: {integrity: sha512-2glYyDYw7QOg6HZ2U1pOnlH5JQ1gSy0BgvtNiKuJvD2LSEwUFxdco3FdwawWdcnByQWTTKqSlR0XKwInewlIBw==, tarball: file:projects/solana-objects.tgz} name: '@rush-temp/solana-objects' version: 0.0.0 dependencies: @@ -7676,7 +7676,7 @@ packages: dev: false file:projects/solana-stream.tgz: - resolution: {integrity: sha512-U65uw7/68o523qpBmA0giMJLSXsiGMuocxQzJhqFhh0Za5eFMzfT77dP2rt0euufNZv+zI7ln2fP/usZfnso+Q==, tarball: file:projects/solana-stream.tgz} + resolution: {integrity: sha512-999rQ9oCT8rZHVCcfiZlW+fT2Q7kdyguZYgP+YU1mX/ci1g6WVtl/1f/pe5VUilUDAhQ7qA3U8RR2YGMqEjXEw==, tarball: file:projects/solana-stream.tgz} name: '@rush-temp/solana-stream' version: 0.0.0 dependencies: @@ -7686,7 +7686,7 @@ packages: dev: false file:projects/solana-typegen.tgz: - resolution: {integrity: sha512-HmURUfd93Y5o6zdSL0NjiPX9kqQdcxQZNkHOVv1dFdI7rKNyH99RQfxqa4y/bFwN9RrlBtem57NmtbGuQQWRdQ==, tarball: file:projects/solana-typegen.tgz} + resolution: {integrity: sha512-OslVSySSUnKqhs6IoKL6vn+SF0+jhpEtm/UiXiI3zhZVXMGgTXMgK68N4hW/QcRFoRlcOs8vwRGamhXJb7I8zA==, tarball: file:projects/solana-typegen.tgz} name: '@rush-temp/solana-typegen' version: 0.0.0 dependencies: @@ -7945,7 +7945,7 @@ packages: dev: false file:projects/tron-usdt.tgz(supports-color@8.1.1)(ts-node@10.9.2): - resolution: {integrity: sha512-h8Fe+t/JEWyOQDVOkB4J1vcVGqf66bG0QNyweXbtFiZR+ZeMxz80wb9DTHqJ82ze4bYmRgtlea4EjhuDqvAelw==, tarball: file:projects/tron-usdt.tgz} + resolution: {integrity: sha512-0QWm4fOm2dQGP2JmOnoj+PiQzlt0sqKkyqxmDHZ1bdjASHQDDLyZcEjqddYJx1UgL51grd1owQ1LH6YCFB7imQ==, tarball: file:projects/tron-usdt.tgz} id: file:projects/tron-usdt.tgz name: '@rush-temp/tron-usdt' version: 0.0.0 @@ -7976,7 +7976,7 @@ packages: dev: false file:projects/typeorm-codegen.tgz: - resolution: {integrity: sha512-e4bfSAXAneZP9rsXz9hzgiBIdmOQ4elj2wt/od3KmjgcEX/69f7DpjqGI+Z1x7hRkfoBdJWTxQfEeLE/AbyUnA==, tarball: file:projects/typeorm-codegen.tgz} + resolution: {integrity: sha512-Y3XSnchEh6rRT5ZaN/cGBQbziEo5e/GRrG99hpwIcy98KIkB6YnjCP7Xx1jaeD8TJ7g0Eu8D5EPy0Iz5cnGM3g==, tarball: file:projects/typeorm-codegen.tgz} name: '@rush-temp/typeorm-codegen' version: 0.0.0 dependencies: diff --git a/graphql/graphql-server/package.json b/graphql/graphql-server/package.json index 6f3e8e7fd..e4a6a3138 100644 --- a/graphql/graphql-server/package.json +++ b/graphql/graphql-server/package.json @@ -48,7 +48,7 @@ }, "peerDependencies": { "@subsquid/big-decimal": "^1.0.0", - "class-validator": "^0.14.0", + "class-validator": "^0.14.2", "type-graphql": "^1.2.0-rc.1", "typeorm": "^0.3.17" }, diff --git a/graphql/graphql-server/src/resolvers.ts b/graphql/graphql-server/src/resolvers.ts index ee2d76833..41aba2ff0 100644 --- a/graphql/graphql-server/src/resolvers.ts +++ b/graphql/graphql-server/src/resolvers.ts @@ -1,5 +1,6 @@ import {GraphQLSchema} from 'graphql' import {buildSchema, ContainerType, ResolverData} from 'type-graphql' +import {validateSync} from 'class-validator' import type {EntityManager} from 'typeorm' import {BigDecimalScalar, BigInteger, Bytes, DateTime} from './scalars' import {TypeormOpenreaderContext} from './typeorm' @@ -23,9 +24,14 @@ export async function loadCustomResolvers(mod: string): Promise { resolvers: [mod], scalarsMap, container: resolverData => new CustomResolversContainer(resolverData), - validate: { - forbidUnknownValues: false - } + validate: ((argValue) => { + const errors = validateSync(argValue!, { + forbidUnknownValues: false + }); + if (errors.length) { + throw errors[0]; + } + }) }) } diff --git a/graphql/graphql-server/src/test/resolvers-extension/lib/server-extension/resolvers.ts b/graphql/graphql-server/src/test/resolvers-extension/lib/server-extension/resolvers.ts index 13b20a907..62c402e32 100644 --- a/graphql/graphql-server/src/test/resolvers-extension/lib/server-extension/resolvers.ts +++ b/graphql/graphql-server/src/test/resolvers-extension/lib/server-extension/resolvers.ts @@ -67,3 +67,11 @@ export class PingPong { return msg } } + +@Resolver() +export class ArgResolver { + @Query(() => Boolean, { nullable: false }) + stringArray(@Arg('ids', () => [String]) ids: string[]): boolean { + return true + } +} diff --git a/graphql/graphql-server/src/test/resolvers.test.ts b/graphql/graphql-server/src/test/resolvers.test.ts index dc7ea7a45..5799600b1 100644 --- a/graphql/graphql-server/src/test/resolvers.test.ts +++ b/graphql/graphql-server/src/test/resolvers.test.ts @@ -13,8 +13,9 @@ describe('resolvers extension', function () { const client = useServer('lib/test/resolvers-extension') - it('scalars', function () { - return client.test(` + describe ('responses', () => { + it('scalars', function () { + return client.test(` query { scalarsExtension { id @@ -27,20 +28,20 @@ describe('resolvers extension', function () { } } `, { - scalarsExtension: [{ - id: '1', - bool: true, - date: '2021-09-24T00:00:00.000Z', - bigInt: '1000000000000000', - bigDecimal: '0.000000000000000001', - bytes: '0xaa', - attributes: [1, 2, 3] - }] + scalarsExtension: [{ + id: '1', + bool: true, + date: '2021-09-24T00:00:00.000Z', + bigInt: '1000000000000000', + bigDecimal: '0.000000000000000001', + bytes: '0xaa', + attributes: [1, 2, 3] + }] + }) }) - }) - it('openreader scalars continue to work', function () { - return client.test(` + it('openreader scalars continue to work', function () { + return client.test(` query { scalars { id @@ -52,19 +53,19 @@ describe('resolvers extension', function () { } } `, { - scalars: [{ - id: '1', - bool: true, - date: '2021-09-24T00:00:00.000000Z', - bigInt: '1000000000000000', - bigDecimal: '0.000000000000000001', - bytes: '0xaa' - }] + scalars: [{ + id: '1', + bool: true, + date: '2021-09-24T00:00:00.000000Z', + bigInt: '1000000000000000', + bigDecimal: '0.000000000000000001', + bytes: '0xaa' + }] + }) }) - }) - it('not enough columns in result regression', function() { - return client.test(` + it('not enough columns in result regression', function () { + return client.test(` query { children { name @@ -74,28 +75,52 @@ describe('resolvers extension', function () { } } `, { - children: [ - { - parent: { - name: 'hello' - }, - name: 'world' - } - ] + children: [ + { + parent: { + name: 'hello' + }, + name: 'world' + } + ] + }) }) - }) - it('ping-pong', function() { - return client.test(` + it('ping-pong', function () { + return client.test(` query { ping(msg: {message: "hello"}) { message } } `, { - ping: { - message: 'hello' - } + ping: { + message: 'hello' + } + }) + }) + }) + + // FIXME multiple describes don't work + describe ('validation', () => { + describe ('@Arg [String]', () => { + it('should pass array', function () { + return client.test(`query { stringArray(ids: ["1"]) }`, {stringArray: true}) + }) + + it('should pass string', function () { + return client.test(`query { stringArray(ids: "1") }`, {stringArray: true}) + }) + + it('should throw if required arg is missing', function () { + return client.httpErrorMatch(`query { stringArray }`, { + errors: [ + { + message: `Field "stringArray" argument "ids" of type "[String!]!" is required, but it was not provided.` + } + ] + }) + }) }) }) }) diff --git a/test/gql-client/src/client.ts b/test/gql-client/src/client.ts index 1f4c33879..ff91d1c1c 100644 --- a/test/gql-client/src/client.ts +++ b/test/gql-client/src/client.ts @@ -28,6 +28,12 @@ export class Client { expect(response.response.body).toEqual(errorData) } + async httpErrorMatch(query: string, errorData: any): Promise { + let response: any = await this.query(query).catch(err => err) + expect(response).toBeInstanceOf(HttpError) + expect(response.response.body).toMatchObject(errorData) + } + subscriptionTest( q: string, test: (take: () => Promise>) => Promise