Fix incorrect schema reference in root POST link
[netconf.git] / restconf / restconf-openapi / src / test / java / org / opendaylight / restconf / openapi / impl / OpenApiGeneratorRFC8040Test.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.openapi.impl;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.mockito.Mockito.mock;
16 import static org.mockito.Mockito.when;
17 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathGetParameters;
18 import static org.opendaylight.restconf.openapi.OpenApiTestUtils.getPathPostParameters;
19 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASIC_AUTH_NAME;
20 import static org.opendaylight.restconf.openapi.model.builder.OperationBuilder.COMPONENTS_PREFIX;
21
22 import com.fasterxml.jackson.databind.JsonNode;
23 import java.util.ArrayList;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import javax.ws.rs.core.UriInfo;
29 import org.junit.BeforeClass;
30 import org.junit.Test;
31 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
32 import org.opendaylight.restconf.openapi.DocGenTestHelper;
33 import org.opendaylight.restconf.openapi.model.OpenApiObject;
34 import org.opendaylight.restconf.openapi.model.Operation;
35 import org.opendaylight.restconf.openapi.model.Path;
36 import org.opendaylight.restconf.openapi.model.Property;
37 import org.opendaylight.restconf.openapi.model.Schema;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
39 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
40
41 public final class OpenApiGeneratorRFC8040Test {
42     private static final String TOASTER_2 = "toaster2";
43     private static final String REVISION_DATE = "2009-11-20";
44     private static final String MANDATORY_TEST = "mandatory-test";
45     private static final String CONFIG_ROOT_CONTAINER = "mandatory-test_root-container";
46     private static final String CONFIG_MANDATORY_CONTAINER = "mandatory-test_root-container_mandatory-container";
47     private static final String CONFIG_MANDATORY_LIST = "mandatory-test_root-container_mandatory-list";
48     private static final String MANDATORY_TEST_MODULE = "mandatory-test_module";
49     private static final String CONTAINER = "container";
50     private static final String LIST = "list";
51
52     private static DOMSchemaService schemaService;
53     private static UriInfo uriInfo;
54
55     private final OpenApiGeneratorRFC8040 generator = new OpenApiGeneratorRFC8040(schemaService);
56
57     @BeforeClass
58     public static void beforeClass() throws Exception {
59         schemaService = mock(DOMSchemaService.class);
60         final EffectiveModelContext context = YangParserTestUtils.parseYangResourceDirectory("/yang");
61         when(schemaService.getGlobalContext()).thenReturn(context);
62         uriInfo = DocGenTestHelper.createMockUriInfo("http://localhost/path");
63     }
64
65     /**
66      * Test that paths are generated according to the model.
67      */
68     @Test
69     public void testPaths() {
70         final OpenApiObject doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
71
72         assertEquals(Set.of("/rests/data",
73             "/rests/data/toaster2:toaster",
74             "/rests/data/toaster2:toaster/toasterSlot={slotId}",
75             "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo",
76             "/rests/data/toaster2:lst={lf1}",
77             "/rests/data/toaster2:lst={lf1}/cont1",
78             "/rests/data/toaster2:lst={lf1}/cont1/cont11",
79             "/rests/data/toaster2:lst={lf1}/cont1/lst11={lf111}",
80             "/rests/data/toaster2:lst={lf1}/lst1={key1},{key2}",
81             "/rests/operations/toaster2:make-toast",
82             "/rests/operations/toaster2:cancel-toast",
83             "/rests/operations/toaster2:restock-toaster"),
84             doc.paths().keySet());
85     }
86
87     /**
88      * Test that generated configuration paths allow to use operations: get, put, patch, delete and post.
89      */
90     @Test
91     public void testConfigPaths() {
92         final List<String> configPaths = List.of("/rests/data/toaster2:lst={lf1}",
93                 "/rests/data/toaster2:lst={lf1}/cont1",
94                 "/rests/data/toaster2:lst={lf1}/cont1/cont11",
95                 "/rests/data/toaster2:lst={lf1}/cont1/lst11={lf111}",
96                 "/rests/data/toaster2:lst={lf1}/lst1={key1},{key2}");
97         final List<String> configPathsForPost = List.of("/rests/data/toaster2:lst={lf1}/cont1",
98                 "/rests/data/toaster2:lst={lf1}/cont1/cont11");
99
100         final OpenApiObject doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
101
102         for (final String path : configPaths) {
103             final Path node = doc.paths().get(path);
104             assertNotNull(node.get());
105             assertNotNull(node.put());
106             assertNotNull(node.delete());
107             assertNotNull(node.patch());
108         }
109
110         for (final String path : configPathsForPost) {
111             final Path node = doc.paths().get(path);
112             assertNotNull(node.post());
113         }
114     }
115
116     /**
117      * Test that generated document contains the following schemas.
118      */
119     @Test
120     public void testSchemas() {
121         final OpenApiObject doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
122
123         final Map<String, Schema> schemas = doc.components().schemas();
124         assertNotNull(schemas);
125
126         final Schema configLst = schemas.get("toaster2_lst");
127         assertNotNull(configLst);
128         DocGenTestHelper.containsReferences(configLst, "lst1", "#/components/schemas/toaster2_lst_lst1");
129         DocGenTestHelper.containsReferences(configLst, "cont1", "#/components/schemas/toaster2_lst_cont1");
130
131         final Schema configLst1 = schemas.get("toaster2_lst_lst1");
132         assertNotNull(configLst1);
133
134         final Schema configCont1 = schemas.get("toaster2_lst_cont1");
135         assertNotNull(configCont1);
136         DocGenTestHelper.containsReferences(configCont1, "cont11", "#/components/schemas/toaster2_lst_cont1_cont11");
137         DocGenTestHelper.containsReferences(configCont1, "lst11", "#/components/schemas/toaster2_lst_cont1_lst11");
138
139         final Schema configCont11 = schemas.get("toaster2_lst_cont1_cont11");
140         assertNotNull(configCont11);
141
142         final Schema configLst11 = schemas.get("toaster2_lst_cont1_lst11");
143         assertNotNull(configLst11);
144     }
145
146     /**
147      * Test that reference to schema in each path is valid (all referenced schemas exist).
148      */
149     @Test
150     public void testSchemasExistenceSingleModule() {
151         final var document = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
152         assertNotNull(document);
153         final var referencedSchemas = new HashSet<String>();
154         for (final var path : document.paths().values()) {
155             referencedSchemas.addAll(extractSchemaRefFromPath(path));
156         }
157         final var schemaNames = document.components().schemas().keySet();
158         for (final var ref : referencedSchemas) {
159             assertTrue("Referenced schema " + ref + " does not exist", schemaNames.contains(ref));
160         }
161     }
162
163     /**
164      * Test that generated document contains RPC schemas for "make-toast" with correct input.
165      */
166     @Test
167     public void testRPC() {
168         final OpenApiObject doc = generator.getApiDeclaration("toaster", "2009-11-20", uriInfo);
169         assertNotNull(doc);
170
171         final Map<String, Schema> schemas = doc.components().schemas();
172         final Schema input = schemas.get("toaster_make-toast_input");
173         final Map<String, Property> properties = input.properties();
174         assertTrue(properties.containsKey("toasterDoneness"));
175         assertTrue(properties.containsKey("toasterToastType"));
176     }
177
178     @Test
179     public void testChoice() {
180         final var doc = generator.getApiDeclaration("choice-test", null, uriInfo);
181         assertNotNull(doc);
182
183         final var schemas = doc.components().schemas();
184         final var firstContainer = schemas.get("choice-test_first-container");
185         assertEquals("default-value", firstContainer.properties().get("leaf-default").defaultValue().toString());
186         assertFalse(firstContainer.properties().containsKey("leaf-non-default"));
187
188         final var secondContainer = schemas.get("choice-test_second-container");
189         assertTrue(secondContainer.properties().containsKey("leaf-first-case"));
190         assertFalse(secondContainer.properties().containsKey("leaf-second-case"));
191     }
192
193     @Test
194     public void testMandatory() {
195         final var doc = generator.getApiDeclaration(MANDATORY_TEST, null, uriInfo);
196         assertNotNull(doc);
197         final var schemas = doc.components().schemas();
198         final var containersWithRequired = new ArrayList<String>();
199
200         final var reqRootContainerElements = List.of("mandatory-root-leaf", "mandatory-container",
201             "mandatory-first-choice", "mandatory-list");
202         verifyRequiredField(schemas.get(CONFIG_ROOT_CONTAINER), reqRootContainerElements);
203         containersWithRequired.add(CONFIG_ROOT_CONTAINER);
204
205         final var reqMandatoryContainerElements = List.of("mandatory-leaf", "leaf-list-with-min-elements");
206         verifyRequiredField(schemas.get(CONFIG_MANDATORY_CONTAINER), reqMandatoryContainerElements);
207         containersWithRequired.add(CONFIG_MANDATORY_CONTAINER);
208
209         final var reqMandatoryListElements = List.of("mandatory-list-field");
210         verifyRequiredField(schemas.get(CONFIG_MANDATORY_LIST), reqMandatoryListElements);
211         containersWithRequired.add(CONFIG_MANDATORY_LIST);
212
213         final var testModuleMandatoryArray = List.of("root-container", "root-mandatory-list");
214         verifyRequiredField(schemas.get(MANDATORY_TEST_MODULE), testModuleMandatoryArray);
215         containersWithRequired.add(MANDATORY_TEST_MODULE);
216
217         verifyThatOthersNodeDoesNotHaveRequiredField(containersWithRequired, schemas);
218     }
219
220     /**
221      * Test that checks for correct amount of parameters in requests.
222      */
223     @Test
224     public void testRecursiveParameters() {
225         final var configPaths = Map.of("/rests/data/recursive:container-root", 0,
226             "/rests/data/recursive:container-root/root-list={name}", 1,
227             "/rests/data/recursive:container-root/root-list={name}/nested-list={name1}", 2,
228             "/rests/data/recursive:container-root/root-list={name}/nested-list={name1}/super-nested-list={name2}", 3);
229
230         final var doc = generator.getApiDeclaration("recursive", "2023-05-22", uriInfo);
231         assertNotNull(doc);
232
233         final var paths = doc.paths();
234         assertEquals(5, paths.size());
235
236         for (final var expectedPath : configPaths.entrySet()) {
237             assertTrue(paths.containsKey(expectedPath.getKey()));
238             final int expectedSize = expectedPath.getValue();
239
240             final var path = paths.get(expectedPath.getKey());
241
242             final var get = path.get();
243             assertNotNull(get);
244             assertEquals(expectedSize + 1, get.parameters().size());
245
246             final var put = path.put();
247             assertNotNull(put);
248             assertEquals(expectedSize, put.parameters().size());
249
250             final var delete = path.delete();
251             assertNotNull(delete);
252             assertEquals(expectedSize, delete.parameters().size());
253
254             final var patch = path.patch();
255             assertNotNull(patch);
256             assertEquals(expectedSize, patch.parameters().size());
257         }
258
259         // we do not generate POST for lists
260         final var path = paths.get("/rests/data/recursive:container-root");
261         final var post = path.post();
262         final int expectedSize = configPaths.get("/rests/data/recursive:container-root");
263         assertEquals(expectedSize, post.parameters().size());
264     }
265
266     /**
267      * Test that request parameters are correctly numbered.
268      *
269      * <p>
270      * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
271      */
272     @Test
273     public void testParametersNumbering() {
274         final var doc = generator.getApiDeclaration("path-params-test", null, uriInfo);
275
276         var pathToList1 = "/rests/data/path-params-test:cont/list1={name}";
277         assertTrue(doc.paths().containsKey(pathToList1));
278         assertEquals(List.of("name"), getPathGetParameters(doc.paths(), pathToList1));
279
280         var pathToList2 = "/rests/data/path-params-test:cont/list1={name}/list2={name1}";
281         assertTrue(doc.paths().containsKey(pathToList2));
282         assertEquals(List.of("name", "name1"), getPathGetParameters(doc.paths(), pathToList2));
283
284         var pathToList3 = "/rests/data/path-params-test:cont/list3={name}";
285         assertTrue(doc.paths().containsKey(pathToList3));
286         assertEquals(List.of("name"), getPathGetParameters(doc.paths(), pathToList3));
287
288         var pathToList4 = "/rests/data/path-params-test:cont/list1={name}/list4={name1}";
289         assertTrue(doc.paths().containsKey(pathToList4));
290         assertEquals(List.of("name", "name1"), getPathGetParameters(doc.paths(), pathToList4));
291
292         var pathToList5 = "/rests/data/path-params-test:cont/list1={name}/cont2";
293         assertTrue(doc.paths().containsKey(pathToList4));
294         assertEquals(List.of("name"), getPathGetParameters(doc.paths(), pathToList5));
295     }
296
297     /**
298      * Test that request for actions is correct and has parameters.
299      */
300     @Test
301     public void testActionPathsParams() {
302         final var doc = generator.getApiDeclaration("action-types", null, uriInfo);
303
304         final var pathWithParameters = "/rests/operations/action-types:list={name}/list-action";
305         assertTrue(doc.paths().containsKey(pathWithParameters));
306         assertEquals(List.of("name"), getPathPostParameters(doc.paths(), pathWithParameters));
307
308         final var pathWithoutParameters = "/rests/operations/action-types:multi-container/inner-container/action";
309         assertTrue(doc.paths().containsKey(pathWithoutParameters));
310         assertEquals(List.of(), getPathPostParameters(doc.paths(), pathWithoutParameters));
311     }
312
313     @Test
314     public void testSimpleOpenApiObjects() {
315         final var doc = generator.getApiDeclaration("my-yang", "2022-10-06", uriInfo);
316
317         assertEquals(Set.of("/rests/data", "/rests/data/my-yang:data"), doc.paths().keySet());
318         final var JsonNodeMyYangData = doc.paths().get("/rests/data/my-yang:data");
319         verifyPostDataRequestRef(JsonNodeMyYangData.post(), "#/components/schemas/my-yang_data",
320             "#/components/schemas/my-yang_data");
321         verifyRequestRef(JsonNodeMyYangData.put(), "#/components/schemas/my-yang_data", CONTAINER);
322         verifyRequestRef(JsonNodeMyYangData.get(), "#/components/schemas/my-yang_data", CONTAINER);
323
324         // Test `components/schemas` objects
325         final var definitions = doc.components().schemas();
326         assertEquals(2, definitions.size());
327         assertTrue(definitions.containsKey("my-yang_data"));
328         assertTrue(definitions.containsKey("my-yang_module"));
329     }
330
331     @Test
332     public void testToaster2OpenApiObjects() {
333         final var doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
334
335         final var jsonNodeToaster = doc.paths().get("/rests/data/toaster2:toaster");
336         verifyRequestRef(jsonNodeToaster.post(), "#/components/schemas/toaster2_toaster_toasterSlot", LIST);
337         verifyRequestRef(jsonNodeToaster.put(), "#/components/schemas/toaster2_toaster", CONTAINER);
338         verifyRequestRef(jsonNodeToaster.get(), "#/components/schemas/toaster2_toaster", CONTAINER);
339
340         final var jsonNodeToasterSlot = doc.paths().get("/rests/data/toaster2:toaster/toasterSlot={slotId}");
341         verifyRequestRef(jsonNodeToasterSlot.put(), "#/components/schemas/toaster2_toaster_toasterSlot", LIST);
342         verifyRequestRef(jsonNodeToasterSlot.get(), "#/components/schemas/toaster2_toaster_toasterSlot", LIST);
343
344         final var jsonNodeSlotInfo = doc.paths().get(
345             "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo");
346         verifyPostDataRequestRef(jsonNodeSlotInfo.post(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
347             "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo");
348         verifyRequestRef(jsonNodeSlotInfo.put(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
349             CONTAINER);
350         verifyRequestRef(jsonNodeSlotInfo.get(), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo",
351             CONTAINER);
352
353         final var jsonNodeLst = doc.paths().get("/rests/data/toaster2:lst={lf1}");
354         verifyRequestRef(jsonNodeLst.put(), "#/components/schemas/toaster2_lst", LIST);
355         verifyRequestRef(jsonNodeLst.get(), "#/components/schemas/toaster2_lst", LIST);
356
357         final var jsonNodeLst1 = doc.paths().get("/rests/data/toaster2:lst={lf1}/lst1={key1},{key2}");
358         verifyRequestRef(jsonNodeLst1.put(), "#/components/schemas/toaster2_lst_lst1", LIST);
359         verifyRequestRef(jsonNodeLst1.get(), "#/components/schemas/toaster2_lst_lst1", LIST);
360
361         final var jsonNodeMakeToast = doc.paths().get("/rests/operations/toaster2:make-toast");
362         assertNull(jsonNodeMakeToast.get());
363         verifyRequestRef(jsonNodeMakeToast.post(), "#/components/schemas/toaster2_make-toast_input", CONTAINER);
364
365         final var jsonNodeCancelToast = doc.paths().get("/rests/operations/toaster2:cancel-toast");
366         assertNull(jsonNodeCancelToast.get());
367         // Test RPC with empty input
368         final var postContent = jsonNodeCancelToast.post().requestBody().get("content");
369         final var jsonSchema = postContent.get("application/json").get("schema");
370         assertNull(jsonSchema.get("$ref"));
371         assertEquals(2, jsonSchema.size());
372         final var xmlSchema = postContent.get("application/xml").get("schema");
373         assertNull(xmlSchema.get("$ref"));
374         assertEquals(2, xmlSchema.size());
375
376         // Test `components/schemas` objects
377         final var definitions = doc.components().schemas();
378         assertEquals(18, definitions.size());
379     }
380
381     /**
382      * Test that checks if securitySchemes and security elements are present.
383      */
384     @Test
385     public void testAuthenticationFeature() {
386         final var doc = generator.getApiDeclaration(TOASTER_2, REVISION_DATE, uriInfo);
387
388         assertEquals("[{basicAuth=[]}]", doc.security().toString());
389         assertEquals("Http[type=http, scheme=basic, description=null, bearerFormat=null]",
390             doc.components().securitySchemes().get(BASIC_AUTH_NAME).toString());
391
392         // take list of all defined security scheme objects => all names of registered SecuritySchemeObjects
393         final var securitySchemesObjectNames = doc.components().securitySchemes().keySet();
394         assertTrue("No Security Schemes Object is defined", securitySchemesObjectNames.size() > 0);
395
396         // collect all referenced security scheme objects
397         final var referencedSecurityObjects = new HashSet<String>();
398         doc.security().forEach(map -> referencedSecurityObjects.addAll(map.keySet()));
399
400         // verify, that each reference references name of registered Security Scheme Object
401         for (final var secObjRef : referencedSecurityObjects) {
402             assertTrue(securitySchemesObjectNames.contains(secObjRef));
403         }
404     }
405
406     /**
407      *  Test JSON and XML references for request operation.
408      */
409     private static void verifyPostDataRequestRef(final Operation operation, final String expectedJsonRef,
410             final String expectedXmlRef) {
411         final JsonNode postContent;
412         if (operation.requestBody() != null) {
413             postContent = operation.requestBody().get("content");
414         } else {
415             postContent = operation.responses().get("200").get("content");
416         }
417         assertNotNull(postContent);
418         final var postJsonRef = postContent.get("application/json").get("schema").get("$ref");
419         assertNotNull(postJsonRef);
420         assertEquals(expectedJsonRef, postJsonRef.textValue());
421         final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
422         assertNotNull(postXmlRef);
423         assertEquals(expectedXmlRef, postXmlRef.textValue());
424     }
425
426     private static void verifyRequestRef(final Operation operation, final String expectedRef, final String nodeType) {
427         final JsonNode postContent;
428         if (operation.requestBody() != null) {
429             postContent = operation.requestBody().path("content");
430         } else {
431             postContent = operation.responses().path("200").path("content");
432         }
433         assertNotNull(postContent);
434         final String postJsonRef;
435         if (nodeType.equals(CONTAINER)) {
436             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
437                 .path("$ref").textValue();
438         } else {
439             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
440                 .path("items").path("$ref").textValue();
441         }
442         assertNotNull(postJsonRef);
443         assertEquals(expectedRef, postJsonRef);
444         final var postXmlRef = postContent.path("application/xml").path("schema").path("$ref");
445         assertNotNull(postXmlRef);
446         assertEquals(expectedRef, postXmlRef.textValue());
447     }
448
449     private static void verifyThatOthersNodeDoesNotHaveRequiredField(final List<String> expected,
450             final Map<String, Schema> schemas) {
451         for (final var schema : schemas.entrySet()) {
452             if (expected.contains(schema.getKey())) {
453                 continue;
454             }
455             assertNull("Json node " + schema.getKey() + " should not have 'required' field in body",
456                 schema.getValue().required());
457         }
458     }
459
460     private static void verifyRequiredField(final Schema rootContainer, final List<String> expected) {
461         assertNotNull(rootContainer);
462         final var required = rootContainer.required();
463         assertNotNull(required);
464         assertEquals(expected, required);
465     }
466
467     private static Set<String> extractSchemaRefFromPath(final Path path) {
468         if (path == null) {
469             return Set.of();
470         }
471         final var references = new HashSet<String>();
472         final var get = path.get();
473         if (get != null) {
474             references.addAll(schemaRefFromContent(get.responses().path("200").path("content")));
475         }
476         final var post = path.post();
477         if (post != null) {
478             references.addAll(schemaRefFromContent(post.requestBody().path("content")));
479         }
480         final var put = path.put();
481         if (put != null) {
482             references.addAll(schemaRefFromContent(put.requestBody().path("content")));
483         }
484         final var patch = path.patch();
485         if (patch != null) {
486             references.addAll(schemaRefFromContent(patch.requestBody().path("content")));
487         }
488         return references;
489     }
490
491     /**
492      * The schema node does not have 1 specific structure and the "$ref" child is not always the first child after
493      * schema. Possible schema structures include:
494      * <ul>
495      *   <li>schema/$ref/{reference}</li>
496      *   <li>schema/properties/{nodeName}/$ref/{reference}</li>
497      *   <li>schema/properties/{nodeName}/items/$ref/{reference}</li>
498      * </ul>
499      * @param content the element identified with key "content"
500      * @return the set of referenced schemas
501      */
502     private static Set<String> schemaRefFromContent(final JsonNode content) {
503         final HashSet<String> refs = new HashSet<>();
504         content.fieldNames().forEachRemaining(mediaType -> {
505             final JsonNode schema = content.path(mediaType).path("schema");
506             final JsonNode props = schema.path("properties");
507             final JsonNode nameNode = props.isMissingNode() ? props : props.elements().next().path("items");
508             final JsonNode ref;
509             if (props.isMissingNode()) {
510                 // either there is no node with the key "properties", try to find immediate child of schema
511                 ref = schema.path("$ref");
512             } else if (nameNode.path("items").isMissingNode()) {
513                 // or the "properties" is defined and under that we didn't find the "items" node
514                 // try to get "$ref" as immediate child under nameNode
515                 ref = nameNode.path("$ref");
516             } else {
517                 // or the "items" node is defined, in which case we try to get the "$ref" from this node
518                 ref = nameNode.path("items").path("$ref");
519             }
520
521             if (ref != null && !ref.isMissingNode()) {
522                 refs.add(ref.asText().replaceFirst(COMPONENTS_PREFIX, ""));
523             }
524         });
525         return refs;
526     }
527 }