3838import java .time .LocalTime ;
3939import java .util .ArrayList ;
4040import java .util .Arrays ;
41+ import java .util .Collection ;
4142import java .util .HashSet ;
4243import java .util .List ;
4344import java .util .Map ;
5051import java .util .concurrent .atomic .AtomicInteger ;
5152import java .util .concurrent .atomic .AtomicLong ;
5253import java .util .function .Predicate ;
54+ import java .util .stream .Collectors ;
5355import java .util .stream .Stream ;
5456
5557import io .swagger .v3 .core .util .PrimitiveType ;
5658import io .swagger .v3 .oas .annotations .Parameter ;
59+ import io .swagger .v3 .oas .annotations .media .Schema ;
5760
5861import org .springframework .core .GenericTypeResolver ;
5962import org .springframework .core .MethodParameter ;
6063
64+ import static org .springdoc .core .service .AbstractRequestService .hasNotNullAnnotation ;
6165import static org .springdoc .core .utils .Constants .DOT ;
6266
6367/**
6468 * The type Method parameter pojo extractor.
6569 *
66- * @author bnasslahsen
70+ * @author bnasslahsen, michael.clarke
6771 */
6872public class MethodParameterPojoExtractor {
6973
@@ -113,20 +117,21 @@ private MethodParameterPojoExtractor() {
113117 * @return the stream
114118 */
115119 static Stream <MethodParameter > extractFrom (Class <?> clazz ) {
116- return extractFrom (clazz , "" );
120+ return extractFrom (clazz , "" , true );
117121 }
118122
119123 /**
120124 * Extract from stream.
121125 *
122126 * @param clazz the clazz
123127 * @param fieldNamePrefix the field name prefix
128+ * @param parentRequired whether the field that hold the class currently being inspected was required or optional
124129 * @return the stream
125130 */
126- private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix ) {
131+ private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix , boolean parentRequired ) {
127132 return allFieldsOf (clazz ).stream ()
128133 .filter (field -> !field .getType ().equals (clazz ))
129- .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix ))
134+ .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix , parentRequired ))
130135 .filter (Objects ::nonNull );
131136 }
132137
@@ -136,20 +141,92 @@ private static Stream<MethodParameter> extractFrom(Class<?> clazz, String fieldN
136141 * @param paramClass the param class
137142 * @param field the field
138143 * @param fieldNamePrefix the field name prefix
144+ * @param parentRequired whether the field that holds the class currently being examined was required or optional
139145 * @return the stream
140146 */
141- private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix ) {
147+ private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix , boolean parentRequired ) {
142148 Class <?> type = extractType (paramClass , field );
143149
144150 if (Objects .isNull (type ))
145151 return Stream .empty ();
146152
147153 if (isSimpleType (type ))
148- return fromSimpleClass (paramClass , field , fieldNamePrefix );
154+ return fromSimpleClass (paramClass , field , fieldNamePrefix , parentRequired );
149155 else {
150- String prefix = fieldNamePrefix + field .getName () + DOT ;
151- return extractFrom (type , prefix );
156+ Parameter parameter = field .getAnnotation (Parameter .class );
157+ Schema schema = field .getAnnotation (Schema .class );
158+ boolean visible = resolveVisible (parameter , schema );
159+ if (!visible ) {
160+ return Stream .empty ();
161+ }
162+ String prefix = fieldNamePrefix + resolveName (parameter , schema ).orElse (field .getName ()) + DOT ;
163+ boolean isNullable = isNullable (field .getDeclaredAnnotations ());
164+ return extractFrom (type , prefix , parentRequired && resolveRequired (schema , parameter , isNullable ));
165+ }
166+ }
167+
168+ private static Optional <String > resolveName (Parameter parameter , Schema schema ) {
169+ if (parameter != null ) {
170+ return resolveNameFromParameter (parameter );
171+ }
172+ if (schema != null ) {
173+ return resolveNameFromSchema (schema );
174+ }
175+ return Optional .empty ();
176+ }
177+
178+ private static Optional <String > resolveNameFromParameter (Parameter parameter ) {
179+ if (parameter .name ().isEmpty ()) {
180+ return Optional .empty ();
181+ }
182+ return Optional .of (parameter .name ());
183+ }
184+
185+ private static Optional <String > resolveNameFromSchema (Schema schema ) {
186+ if (schema .name ().isEmpty ()) {
187+ return Optional .empty ();
188+ }
189+ return Optional .of (schema .name ());
190+ }
191+
192+ private static boolean resolveVisible (Parameter parameter , Schema schema ) {
193+ if (parameter != null ) {
194+ return !parameter .hidden ();
195+ }
196+ if (schema != null ) {
197+ return !schema .hidden ();
198+ }
199+ return true ;
200+ }
201+
202+ private static boolean resolveRequired (Schema schema , Parameter parameter , boolean nullable ) {
203+ if (parameter != null ) {
204+ return resolveRequiredFromParameter (parameter , nullable );
205+ }
206+ if (schema != null ) {
207+ return resolveRequiredFromSchema (schema , nullable );
152208 }
209+ return !nullable ;
210+ }
211+
212+ private static boolean resolveRequiredFromParameter (Parameter parameter , boolean nullable ) {
213+ if (parameter .required ()) {
214+ return true ;
215+ }
216+ return !nullable ;
217+ }
218+
219+ private static boolean resolveRequiredFromSchema (Schema schema , boolean nullable ) {
220+ if (schema .required ()) {
221+ return true ;
222+ }
223+ else if (schema .requiredMode () == Schema .RequiredMode .REQUIRED ) {
224+ return true ;
225+ }
226+ else if (schema .requiredMode () == Schema .RequiredMode .NOT_REQUIRED ) {
227+ return false ;
228+ }
229+ return !nullable ;
153230 }
154231
155232 /**
@@ -181,19 +258,20 @@ private static Class<?> extractType(Class<?> paramClass, Field field) {
181258 * @param fieldNamePrefix the field name prefix
182259 * @return the stream
183260 */
184- private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix ) {
261+ private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix , boolean isParentRequired ) {
185262 Annotation [] fieldAnnotations = field .getDeclaredAnnotations ();
186263 try {
187264 Parameter parameter = field .getAnnotation (Parameter .class );
188- boolean isNotRequired = parameter == null || !parameter .required ();
265+ Schema schema = field .getAnnotation (Schema .class );
266+ boolean isNullable = isNullable (fieldAnnotations );
267+ boolean isNotRequired = !(isParentRequired && resolveRequired (schema , parameter , isNullable ));
189268 if (paramClass .getSuperclass () != null && paramClass .isRecord ()) {
190269 return Stream .of (paramClass .getRecordComponents ())
191270 .filter (d -> d .getName ().equals (field .getName ()))
192271 .map (RecordComponent ::getAccessor )
193272 .map (method -> new MethodParameter (method , -1 ))
194273 .map (methodParameter -> DelegatingMethodParameter .changeContainingClass (methodParameter , paramClass ))
195274 .map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), true , isNotRequired ));
196-
197275 }
198276 else
199277 return Stream .of (Introspector .getBeanInfo (paramClass ).getPropertyDescriptors ())
@@ -273,4 +351,17 @@ public static void removeSimpleTypes(Class<?>... classes) {
273351 SIMPLE_TYPES .removeAll (Arrays .asList (classes ));
274352 }
275353
354+ /**
355+ * Is nullable boolean.
356+ *
357+ * @param fieldAnnotations the field annotations
358+ * @return the boolean
359+ */
360+ private static boolean isNullable (Annotation [] fieldAnnotations ) {
361+ Collection <String > annotationSimpleNames = Arrays .stream (fieldAnnotations )
362+ .map (Annotation ::annotationType )
363+ .map (Class ::getSimpleName )
364+ .collect (Collectors .toSet ());
365+ return !hasNotNullAnnotation (annotationSimpleNames );
366+ }
276367}
0 commit comments