From b5b7b8260a4549f3bd7443fbd68be5ccc9857cd7 Mon Sep 17 00:00:00 2001 From: Convly Date: Wed, 17 Jan 2024 11:53:34 +0100 Subject: [PATCH] fix: force SetMinMax to use the correct primitive at generation --- .../generators/schemas/attributes.test.js | 138 ++++++++++++------ .../generators/common/models/attributes.js | 33 ++++- 2 files changed, 125 insertions(+), 46 deletions(-) diff --git a/packages/utils/typescript/lib/__tests__/generators/schemas/attributes.test.js b/packages/utils/typescript/lib/__tests__/generators/schemas/attributes.test.js index e4758a174e..6c6e241e51 100644 --- a/packages/utils/typescript/lib/__tests__/generators/schemas/attributes.test.js +++ b/packages/utils/typescript/lib/__tests__/generators/schemas/attributes.test.js @@ -548,19 +548,27 @@ describe('Attributes', () => { expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference); expect(modifiers[0].typeName.escapedText).toBe('Attribute.SetMinMax'); - expect(modifiers[0].typeArguments).toHaveLength(1); - expect(modifiers[0].typeArguments[0].kind).toBe(ts.SyntaxKind.TypeLiteral); - expect(modifiers[0].typeArguments[0].members).toHaveLength(1); + const [setMinMax] = modifiers; + const { typeArguments } = setMinMax; + + expect(typeArguments).toBeDefined(); + expect(typeArguments).toHaveLength(2); + + const [definition, typeofMinMax] = typeArguments; // Min - expect(modifiers[0].typeArguments[0].members[0].kind).toBe( - ts.SyntaxKind.PropertyDeclaration - ); - expect(modifiers[0].typeArguments[0].members[0].name.escapedText).toBe('min'); - expect(modifiers[0].typeArguments[0].members[0].type.kind).toBe( - ts.SyntaxKind.NumericLiteral - ); - expect(modifiers[0].typeArguments[0].members[0].type.text).toBe('2'); + expect(definition.kind).toBe(ts.SyntaxKind.TypeLiteral); + expect(definition.members).toHaveLength(1); + + const [min] = definition.members; + + expect(min.kind).toBe(ts.SyntaxKind.PropertyDeclaration); + expect(min.name.escapedText).toBe('min'); + expect(min.type.kind).toBe(ts.SyntaxKind.NumericLiteral); + expect(min.type.text).toBe('2'); + + // Check for number keyword on the second typeArgument + expect(typeofMinMax.kind).toBe(ts.SyntaxKind.NumberKeyword); }); test('No Min, Max: 3', () => { @@ -572,19 +580,27 @@ describe('Attributes', () => { expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference); expect(modifiers[0].typeName.escapedText).toBe('Attribute.SetMinMax'); - expect(modifiers[0].typeArguments).toHaveLength(1); - expect(modifiers[0].typeArguments[0].kind).toBe(ts.SyntaxKind.TypeLiteral); - expect(modifiers[0].typeArguments[0].members).toHaveLength(1); + const [setMinMax] = modifiers; + const { typeArguments } = setMinMax; - // Min - expect(modifiers[0].typeArguments[0].members[0].kind).toBe( - ts.SyntaxKind.PropertyDeclaration - ); - expect(modifiers[0].typeArguments[0].members[0].name.escapedText).toBe('max'); - expect(modifiers[0].typeArguments[0].members[0].type.kind).toBe( - ts.SyntaxKind.NumericLiteral - ); - expect(modifiers[0].typeArguments[0].members[0].type.text).toBe('3'); + expect(typeArguments).toBeDefined(); + expect(typeArguments).toHaveLength(2); + + const [definition, typeofMinMax] = typeArguments; + + // Max + expect(definition.kind).toBe(ts.SyntaxKind.TypeLiteral); + expect(definition.members).toHaveLength(1); + + const [max] = definition.members; + + expect(max.kind).toBe(ts.SyntaxKind.PropertyDeclaration); + expect(max.name.escapedText).toBe('max'); + expect(max.type.kind).toBe(ts.SyntaxKind.NumericLiteral); + expect(max.type.text).toBe('3'); + + // Check for number keyword on the second typeArgument + expect(typeofMinMax.kind).toBe(ts.SyntaxKind.NumberKeyword); }); test('Min: 4, Max: 12', () => { @@ -596,28 +612,64 @@ describe('Attributes', () => { expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference); expect(modifiers[0].typeName.escapedText).toBe('Attribute.SetMinMax'); - expect(modifiers[0].typeArguments).toHaveLength(1); - expect(modifiers[0].typeArguments[0].kind).toBe(ts.SyntaxKind.TypeLiteral); - expect(modifiers[0].typeArguments[0].members).toHaveLength(2); + const [setMinMax] = modifiers; + const { typeArguments } = setMinMax; - // Min - expect(modifiers[0].typeArguments[0].members[0].kind).toBe( - ts.SyntaxKind.PropertyDeclaration - ); - expect(modifiers[0].typeArguments[0].members[0].name.escapedText).toBe('min'); - expect(modifiers[0].typeArguments[0].members[0].type.kind).toBe( - ts.SyntaxKind.NumericLiteral - ); - expect(modifiers[0].typeArguments[0].members[0].type.text).toBe('4'); + expect(typeArguments).toBeDefined(); + expect(typeArguments).toHaveLength(2); - expect(modifiers[0].typeArguments[0].members[1].kind).toBe( - ts.SyntaxKind.PropertyDeclaration - ); - expect(modifiers[0].typeArguments[0].members[1].name.escapedText).toBe('max'); - expect(modifiers[0].typeArguments[0].members[1].type.kind).toBe( - ts.SyntaxKind.NumericLiteral - ); - expect(modifiers[0].typeArguments[0].members[1].type.text).toBe('12'); + const [definition, typeofMinMax] = typeArguments; + + // Min/Max + expect(definition.kind).toBe(ts.SyntaxKind.TypeLiteral); + expect(definition.members).toHaveLength(2); + + const [min, max] = definition.members; + + expect(min.kind).toBe(ts.SyntaxKind.PropertyDeclaration); + expect(min.name.escapedText).toBe('min'); + expect(min.type.kind).toBe(ts.SyntaxKind.NumericLiteral); + expect(min.type.text).toBe('4'); + + expect(max.kind).toBe(ts.SyntaxKind.PropertyDeclaration); + expect(max.name.escapedText).toBe('max'); + expect(max.type.kind).toBe(ts.SyntaxKind.NumericLiteral); + expect(max.type.text).toBe('12'); + + // Check for number keyword on the second typeArgument + expect(typeofMinMax.kind).toBe(ts.SyntaxKind.NumberKeyword); + }); + + test('Min: "1"', () => { + const attribute = { min: '1' }; + const modifiers = getAttributeModifiers(attribute); + + expect(modifiers).toHaveLength(1); + + expect(modifiers[0].kind).toBe(ts.SyntaxKind.TypeReference); + expect(modifiers[0].typeName.escapedText).toBe('Attribute.SetMinMax'); + + const [setMinMax] = modifiers; + const { typeArguments } = setMinMax; + + expect(typeArguments).toBeDefined(); + expect(typeArguments).toHaveLength(2); + + const [definition, typeofMinMax] = typeArguments; + + // Min/Max + expect(definition.kind).toBe(ts.SyntaxKind.TypeLiteral); + expect(definition.members).toHaveLength(1); + + const [min] = definition.members; + + expect(min.kind).toBe(ts.SyntaxKind.PropertyDeclaration); + expect(min.name.escapedText).toBe('min'); + expect(min.type.kind).toBe(ts.SyntaxKind.StringLiteral); + expect(min.type.text).toBe('1'); + + // Check for number keyword on the second typeArgument + expect(typeofMinMax.kind).toBe(ts.SyntaxKind.StringKeyword); }); }); diff --git a/packages/utils/typescript/lib/generators/common/models/attributes.js b/packages/utils/typescript/lib/generators/common/models/attributes.js index 1efdc45d1e..a39f9419f4 100644 --- a/packages/utils/typescript/lib/generators/common/models/attributes.js +++ b/packages/utils/typescript/lib/generators/common/models/attributes.js @@ -1,12 +1,14 @@ 'use strict'; -const { factory } = require('typescript'); +const ts = require('typescript'); const _ = require('lodash/fp'); const { addImport } = require('../imports'); const { getTypeNode, toTypeLiteral, withAttributeNamespace, NAMESPACES } = require('./utils'); const mappers = require('./mappers'); +const { factory } = ts; + /** * Create the base type node for a given attribute * @@ -100,14 +102,39 @@ const getAttributeModifiers = (attribute) => { } // Min / Max - // TODO: Always provide a second type argument for min/max (ie: resolve the attribute scalar type with a `GetAttributeType<${mappers[attribute][0]}>` (useful for biginter (string values))) if (!_.isNil(attribute.min) || !_.isNil(attribute.max)) { const minMaxProperties = _.pick(['min', 'max'], attribute); + const { min, max } = minMaxProperties; + + const typeofMin = typeof min; + const typeofMax = typeof max; + + // Throws error if min/max exist but have different types to prevent unexpected behavior + if (min !== undefined && max !== undefined && typeofMin !== typeofMax) { + throw new Error('typeof min/max values mismatch'); + } + + const typeofMinMax = (max && typeofMax) ?? (min && typeofMin); + let typeKeyword; + + // Determines type keyword (string/number) based on min/max options, throws error for invalid types + switch (typeofMinMax) { + case 'string': + typeKeyword = ts.SyntaxKind.StringKeyword; + break; + case 'number': + typeKeyword = ts.SyntaxKind.NumberKeyword; + break; + default: + throw new Error( + `Invalid data type for min/max options. Must be string or number, but found ${typeofMinMax}` + ); + } modifiers.push( factory.createTypeReferenceNode( factory.createIdentifier(withAttributeNamespace('SetMinMax')), - [toTypeLiteral(minMaxProperties)] + [toTypeLiteral(minMaxProperties), factory.createKeywordTypeNode(typeKeyword)] ) ); }