5050import java .util .concurrent .atomic .AtomicInteger ;
5151import java .util .concurrent .atomic .AtomicLong ;
5252import java .util .function .Predicate ;
53+ import java .util .stream .Collectors ;
5354import java .util .stream .Stream ;
5455
5556import io .swagger .v3 .core .util .PrimitiveType ;
5657import io .swagger .v3 .oas .annotations .Parameter ;
58+ import io .swagger .v3 .oas .annotations .media .Schema ;
59+ import org .springdoc .core .service .AbstractRequestService ;
5760
5861import org .springframework .core .GenericTypeResolver ;
5962import org .springframework .core .MethodParameter ;
6366/**
6467 * The type Method parameter pojo extractor.
6568 *
66- * @author bnasslahsen
69+ * @author bnasslahsen, michael.clarke
6770 */
6871public class MethodParameterPojoExtractor {
6972
@@ -113,20 +116,21 @@ private MethodParameterPojoExtractor() {
113116 * @return the stream
114117 */
115118 static Stream <MethodParameter > extractFrom (Class <?> clazz ) {
116- return extractFrom (clazz , "" );
119+ return extractFrom (clazz , "" , true );
117120 }
118121
119122 /**
120123 * Extract from stream.
121124 *
122125 * @param clazz the clazz
123126 * @param fieldNamePrefix the field name prefix
127+ * @param parentRequired whether the field that hold the class currently being inspected was required or optional
124128 * @return the stream
125129 */
126- private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix ) {
130+ private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix , boolean parentRequired ) {
127131 return allFieldsOf (clazz ).stream ()
128132 .filter (field -> !field .getType ().equals (clazz ))
129- .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix ))
133+ .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix , parentRequired ))
130134 .filter (Objects ::nonNull );
131135 }
132136
@@ -136,20 +140,95 @@ private static Stream<MethodParameter> extractFrom(Class<?> clazz, String fieldN
136140 * @param paramClass the param class
137141 * @param field the field
138142 * @param fieldNamePrefix the field name prefix
143+ * @param parentRequired whether the field that holds the class currently being examined was required or optional
139144 * @return the stream
140145 */
141- private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix ) {
146+ private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix , boolean parentRequired ) {
142147 Class <?> type = extractType (paramClass , field );
143148
144149 if (Objects .isNull (type ))
145150 return Stream .empty ();
146151
147152 if (isSimpleType (type ))
148- return fromSimpleClass (paramClass , field , fieldNamePrefix );
153+ return fromSimpleClass (paramClass , field , fieldNamePrefix , parentRequired );
149154 else {
150- String prefix = fieldNamePrefix + field .getName () + DOT ;
151- return extractFrom (type , prefix );
155+ Parameter parameter = field .getAnnotation (Parameter .class );
156+ Schema schema = field .getAnnotation (Schema .class );
157+ boolean visible = resolveVisible (parameter , schema );
158+ if (!visible ) {
159+ return Stream .empty ();
160+ }
161+ String prefix = fieldNamePrefix + resolveName (parameter , schema ).orElse (field .getName ()) + DOT ;
162+ boolean notNullAnnotationsPresent = AbstractRequestService .hasNotNullAnnotation (Arrays .stream (field .getDeclaredAnnotations ())
163+ .map (Annotation ::annotationType )
164+ .map (Class ::getSimpleName )
165+ .collect (Collectors .toSet ()));
166+ return extractFrom (type , prefix , parentRequired && resolveRequired (schema , parameter , !notNullAnnotationsPresent ));
167+ }
168+ }
169+
170+ private static Optional <String > resolveName (Parameter parameter , Schema schema ) {
171+ if (parameter != null ) {
172+ return resolveNameFromParameter (parameter );
173+ }
174+ if (schema != null ) {
175+ return resolveNameFromSchema (schema );
176+ }
177+ return Optional .empty ();
178+ }
179+
180+ private static Optional <String > resolveNameFromParameter (Parameter parameter ) {
181+ if (parameter .name ().isEmpty ()) {
182+ return Optional .empty ();
183+ }
184+ return Optional .of (parameter .name ());
185+ }
186+
187+ private static Optional <String > resolveNameFromSchema (Schema schema ) {
188+ if (schema .name ().isEmpty ()) {
189+ return Optional .empty ();
190+ }
191+ return Optional .of (schema .name ());
192+ }
193+
194+ private static boolean resolveVisible (Parameter parameter , Schema schema ) {
195+ if (parameter != null ) {
196+ return !parameter .hidden ();
152197 }
198+ if (schema != null ) {
199+ return !schema .hidden ();
200+ }
201+ return true ;
202+ }
203+
204+ private static boolean resolveRequired (Schema schema , Parameter parameter , boolean nullable ) {
205+ if (parameter != null ) {
206+ return resolveRequiredFromParameter (parameter , nullable );
207+ }
208+ if (schema != null ) {
209+ return resolveRequiredFromSchema (schema , nullable );
210+ }
211+ return !nullable ;
212+ }
213+
214+ private static boolean resolveRequiredFromParameter (Parameter parameter , boolean nullable ) {
215+ if (parameter .required ()) {
216+ return true ;
217+ }
218+ return !nullable ;
219+ }
220+
221+ private static boolean resolveRequiredFromSchema (Schema schema , boolean nullable ) {
222+ if (schema .required ()) {
223+ return true ;
224+ }
225+ else if (schema .requiredMode () == Schema .RequiredMode .REQUIRED ) {
226+ return true ;
227+ }
228+ else if (schema .requiredMode () == Schema .RequiredMode .NOT_REQUIRED ) {
229+ return false ;
230+ }
231+ return !nullable ;
153232 }
154233
155234 /**
@@ -181,18 +260,30 @@ private static Class<?> extractType(Class<?> paramClass, Field field) {
181260 * @param fieldNamePrefix the field name prefix
182261 * @return the stream
183262 */
184- private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix ) {
263+ private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix , boolean isParentRequired ) {
185264 Annotation [] fieldAnnotations = field .getDeclaredAnnotations ();
186265 try {
187266 Parameter parameter = field .getAnnotation (Parameter .class );
188- boolean isNotRequired = parameter == null || !parameter .required ();
267+ Schema schema = field .getAnnotation (Schema .class );
268+ boolean visible = resolveVisible (parameter , schema );
269+ if (!visible ) {
270+ return Stream .empty ();
271+ }
272+
273+ boolean isNotRequired = !(isParentRequired && resolveRequired (schema , parameter , !AbstractRequestService .hasNotNullAnnotation (Arrays .stream (fieldAnnotations )
274+ .map (Annotation ::annotationType )
275+ .map (Class ::getSimpleName )
276+ .collect (Collectors .toSet ()))));
277+ Annotation [] notNullFieldAnnotations = Arrays .stream (fieldAnnotations )
278+ .filter (annotation -> AbstractRequestService .hasNotNullAnnotation (List .of (annotation .annotationType ().getSimpleName ())))
279+ .toArray (Annotation []::new );
189280 if (paramClass .getSuperclass () != null && paramClass .isRecord ()) {
190281 return Stream .of (paramClass .getRecordComponents ())
191282 .filter (d -> d .getName ().equals (field .getName ()))
192283 .map (RecordComponent ::getAccessor )
193284 .map (method -> new MethodParameter (method , -1 ))
194285 .map (methodParameter -> DelegatingMethodParameter .changeContainingClass (methodParameter , paramClass ))
195- .map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), true , isNotRequired ));
286+ .map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), notNullFieldAnnotations , true , isNotRequired ));
196287
197288 }
198289 else
@@ -202,7 +293,7 @@ private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Fiel
202293 .filter (Objects ::nonNull )
203294 .map (method -> new MethodParameter (method , -1 ))
204295 .map (methodParameter -> DelegatingMethodParameter .changeContainingClass (methodParameter , paramClass ))
205- .map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), true , isNotRequired ));
296+ .map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), notNullFieldAnnotations , true , isNotRequired ));
206297 }
207298 catch (IntrospectionException e ) {
208299 return Stream .of ();
0 commit comments