diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index 58181bcdd7..7b6b7cefa9 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -1082,7 +1082,7 @@ private Annotation[] addGenericTypeArgumentAnnotationsForOptionalField(BeanPrope .map(field -> !(field.getType().equals(Optional.class))) .orElse(false); - if (isNotOptionalType) { + if (isNotOptionalType || isRecordType(propDef)) { return annotations; } @@ -1091,6 +1091,9 @@ private Annotation[] addGenericTypeArgumentAnnotationsForOptionalField(BeanPrope } private Stream extractGenericTypeArgumentAnnotations(BeanPropertyDefinition propDef) { + if (isRecordType(propDef)){ + return getRecordComponentAnnotations(propDef); + } return Optional.ofNullable(propDef) .map(BeanPropertyDefinition::getField) .map(AnnotatedField::getAnnotated) @@ -1098,14 +1101,41 @@ private Stream extractGenericTypeArgumentAnnotations(BeanPropertyDef .orElseGet(Stream::of); } + private Stream getRecordComponentAnnotations(BeanPropertyDefinition propDef) { + try { + Method accessor = propDef.getPrimaryMember().getDeclaringClass().getDeclaredMethod(propDef.getName()); + return getGenericTypeArgumentAnnotations(accessor.getAnnotatedReturnType()); + } catch (NoSuchMethodException e) { + LOGGER.error("Accessor for record component not found"); + return Stream.empty(); + } + } + + private Boolean isRecordType(BeanPropertyDefinition propDef){ + try { + Class clazz = propDef.getPrimaryMember().getDeclaringClass(); + Method isRecordMethod = Class.class.getMethod("isRecord"); + return (Boolean) isRecordMethod.invoke(clazz); + } catch (NoSuchMethodException e) { + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + private Stream getGenericTypeArgumentAnnotations(Field field) { - return Optional.of(field.getAnnotatedType()) - .filter(annotatedType -> annotatedType instanceof AnnotatedParameterizedType) - .map(annotatedType -> (AnnotatedParameterizedType) annotatedType) - .map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments) - .map(types -> Stream.of(types) - .flatMap(type -> Stream.of(type.getAnnotations()))) - .orElseGet(Stream::of); + return getGenericTypeArgumentAnnotations(field.getAnnotatedType()); + } + + private Stream getGenericTypeArgumentAnnotations(java.lang.reflect.AnnotatedType annotatedType) { + return Optional.of(annotatedType) + .filter(type -> type instanceof AnnotatedParameterizedType) + .map(type -> (AnnotatedParameterizedType) type) + .map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments) + .map(types -> Stream.of(types) + .flatMap(type -> Stream.of(type.getAnnotations()))) + .orElseGet(Stream::of); } private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation) { diff --git a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java index 5e483a1f94..065b6c2ea5 100644 --- a/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java +++ b/modules/swagger-java17-support/src/test/java/io/swagger/v3/java17/resolving/JavaRecordTest.java @@ -80,4 +80,41 @@ public record JavaRecordClassWithBeanValidation( ){ } + @Test + public void testJavaRecordWithBeanValidationSizeTypeUse() { + String expectedYaml = "JavaRecordWithAnnotationsOnGenericType:\n" + + " type: object\n" + + " properties:\n" + + " randomList:\n" + + " maxItems: 10000\n" + + " minItems: 100\n" + + " type: array\n" + + " items:\n" + + " maxLength: 10\n" + + " minLength: 1\n" + + " type: string\n" + + " secondList:\n" + + " type: array\n" + + " items:\n" + + " pattern: (.+?)@(.+?)\n" + + " type: string\n" + + " id:\n" + + " type: array\n" + + " items:\n" + + " maximum: 10000\n" + + " minimum: 1\n" + + " type: integer\n" + + " format: int32"; + + Map stringSchemaMap = ModelConverters.getInstance(false).readAll(JavaRecordWithAnnotationsOnGenericType.class); + SerializationMatchers.assertEqualsToYaml(stringSchemaMap, expectedYaml); + } + + public record JavaRecordWithAnnotationsOnGenericType( + @Size(min = 100, max = 10000) + List<@Size(min = 1, max = 10) String> randomList, + List<@Pattern(regexp = "(.+?)@(.+?)") String> secondList, + List<@Min(1)@Max(10000) Integer> id + ){ + } }