You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/01/07 16:39:35 UTC
[sling-org-apache-sling-graphql-core] branch master updated:
SLING-10018 As a developer I want to access the selectionset in my fetcher
context
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/master by this push:
new 1975b40 SLING-10018 As a developer I want to access the selectionset in my fetcher context
1975b40 is described below
commit 1975b40565e02606e3727591964140b401b50c92
Author: Thierry Ygé <th...@gmail.com>
AuthorDate: Thu Jan 7 17:39:27 2021 +0100
SLING-10018 As a developer I want to access the selectionset in my fetcher context
* implemented SelectionSet / SelectedField wrappers
---
.../apache/sling/graphql/api/SelectedField.java | 65 ++++++++++++++++
.../org/apache/sling/graphql/api/SelectionSet.java | 77 +++++++++++++++++++
.../graphql/api/SlingDataFetcherEnvironment.java | 4 +
.../org/apache/sling/graphql/api/package-info.java | 2 +-
.../engine/DataFetchingEnvironmentWrapper.java | 8 ++
.../graphql/core/engine/SelectedFieldWrapper.java | 89 ++++++++++++++++++++++
.../graphql/core/engine/SelectionSetWrapper.java | 72 +++++++++++++++++
.../core/engine/DefaultQueryExecutorTest.java | 66 ++++++++++++++++
.../sling/graphql/core/mocks/EchoDataFetcher.java | 7 ++
src/test/resources/test-schema.txt | 10 +++
10 files changed, 399 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/sling/graphql/api/SelectedField.java b/src/main/java/org/apache/sling/graphql/api/SelectedField.java
new file mode 100644
index 0000000..b3da627
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/SelectedField.java
@@ -0,0 +1,65 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.api;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
+import java.util.List;
+
+/**
+ * Interface to wrap information from <a href="https://javadoc.io/doc/com.graphql-java/graphql-java/latest/graphql/schema/SelectedField.html">GraphQL SelectedField</a>.
+ *
+ * <p>As described in {@link org.apache.sling.graphql.api.SelectionSet SelectionSet}, it is aimed to map the SelectedField to the minimum information
+ * required when processing the query.</p><p>InlineFragment are mapped so that its isInline() is return true.</p>
+ */
+@ProviderType
+public interface SelectedField {
+
+ /**
+ * @return the name as defined in the selection set.
+ */
+ @Nullable
+ String getName();
+
+ /**
+ * @return the sub selected fields.
+ */
+ @NotNull
+ List<SelectedField> getSubSelectedFields();
+
+ /**
+ * @param name the sub selected field name.
+ * @return the object or null if that doesn't exist.
+ */
+ @Nullable
+ SelectedField getSubSelectedField(String name);
+
+ /**
+ * @param name the sub selected field name(s).
+ * @return true if any of the sub selected fields exists.
+ */
+ boolean hasSubSelectedFields(String ...name);
+
+ /**
+ * @return true if this field is an inline (i.e: ... on Something { }).
+ */
+ boolean isInline();
+}
diff --git a/src/main/java/org/apache/sling/graphql/api/SelectionSet.java b/src/main/java/org/apache/sling/graphql/api/SelectionSet.java
new file mode 100644
index 0000000..ccfa765
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/SelectionSet.java
@@ -0,0 +1,77 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.api;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
+import java.util.List;
+
+/**
+ * Interface to wrap information from <a href="https://javadoc.io/doc/com.graphql-java/graphql-java/latest/graphql/schema/DataFetchingFieldSelectionSet.html">GraphQL DataFetchingFieldSelectionSet</a>.
+ * <p>Mainly it keeps information about fields name that got selected.</p>
+ * <pre>
+ * For example:
+ * {@code
+ * queryName {
+ * field1
+ * field2 {
+ * ... on Type1 {
+ * field3
+ * }
+ * }
+ * field4
+ * field5 {
+ * field6
+ * field7 {
+ * field8
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Would result in a mapping with corresponding SelectedField(s).</p>
+ * <p><b>field1</b> would be accessible with qualified name "field1"
+ * while <b>field3</b> would be accessible with qualified name "field2/Type1/field3"
+ * and <b>field8</b> would be accessible with qualified name "field5/field7/field8"
+ * </p>
+ * <p><b>Type1</b> would be a SelectedField with isInline() returning true</p>
+ */
+@ProviderType
+public interface SelectionSet {
+
+ /**
+ * @return the immediate list of fields in the selection.
+ */
+ @NotNull
+ List<SelectedField> getFields();
+
+ /**
+ * @return true if the field qualified name exist.
+ */
+ boolean contains(String qualifiedName);
+
+ /**
+ * @return SelectedField for qualified name.
+ */
+ @Nullable
+ SelectedField get(String qualifiedName);
+}
diff --git a/src/main/java/org/apache/sling/graphql/api/SlingDataFetcherEnvironment.java b/src/main/java/org/apache/sling/graphql/api/SlingDataFetcherEnvironment.java
index c557145..29bcf54 100644
--- a/src/main/java/org/apache/sling/graphql/api/SlingDataFetcherEnvironment.java
+++ b/src/main/java/org/apache/sling/graphql/api/SlingDataFetcherEnvironment.java
@@ -20,6 +20,7 @@
package org.apache.sling.graphql.api;
import java.util.Map;
+
import org.apache.sling.api.resource.Resource;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;
@@ -64,4 +65,7 @@ public interface SlingDataFetcherEnvironment {
/** @return the source, if set by the schema directive */
@Nullable
String getFetcherSource();
+
+ /** @return the selectionSet, mandatory in a graphql query */
+ SelectionSet getSelectionSet();
}
diff --git a/src/main/java/org/apache/sling/graphql/api/package-info.java b/src/main/java/org/apache/sling/graphql/api/package-info.java
index e5d4b27..39a0bdc 100644
--- a/src/main/java/org/apache/sling/graphql/api/package-info.java
+++ b/src/main/java/org/apache/sling/graphql/api/package-info.java
@@ -21,6 +21,6 @@
* This package contains APIs which are independent of
* a specific implementation of the underlying graphQL engine.
*/
-@Version("3.2.0")
+@Version("3.3.0")
package org.apache.sling.graphql.api;
import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/DataFetchingEnvironmentWrapper.java b/src/main/java/org/apache/sling/graphql/core/engine/DataFetchingEnvironmentWrapper.java
index 560013c..a98d416 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/DataFetchingEnvironmentWrapper.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DataFetchingEnvironmentWrapper.java
@@ -22,6 +22,7 @@ package org.apache.sling.graphql.core.engine;
import java.util.Map;
import org.apache.sling.api.resource.Resource;
+import org.apache.sling.graphql.api.SelectionSet;
import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
import graphql.schema.DataFetchingEnvironment;
@@ -35,12 +36,14 @@ class DataFetchingEnvironmentWrapper implements SlingDataFetcherEnvironment {
private final Resource currentResource;
private final String options;
private final String source;
+ private final SelectionSet selectionSet;
DataFetchingEnvironmentWrapper(DataFetchingEnvironment env, Resource currentResource, String options, String source) {
this.env = env;
this.currentResource = currentResource;
this.options = options;
this.source = source;
+ this.selectionSet = new SelectionSetWrapper(env.getSelectionSet());
}
@Override
@@ -77,4 +80,9 @@ class DataFetchingEnvironmentWrapper implements SlingDataFetcherEnvironment {
public String getFetcherSource() {
return source;
}
+
+ @Override
+ public SelectionSet getSelectionSet() {
+ return selectionSet;
+ }
}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/SelectedFieldWrapper.java b/src/main/java/org/apache/sling/graphql/core/engine/SelectedFieldWrapper.java
new file mode 100644
index 0000000..6ca27a3
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/engine/SelectedFieldWrapper.java
@@ -0,0 +1,89 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.core.engine;
+
+import graphql.language.Field;
+import graphql.language.InlineFragment;
+import graphql.language.Selection;
+import graphql.language.SelectionSet;
+import org.apache.sling.graphql.api.SelectedField;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Implement a wrapper for GraphQL SelectedField.
+ */
+public class SelectedFieldWrapper implements SelectedField {
+
+ private String name;
+ private boolean isInline;
+ private Map<String, SelectedField> subFieldMap = new HashMap<>();
+ private List<SelectedField> subFields;
+
+ public SelectedFieldWrapper(Selection selection) {
+ SelectionSet selectionSet = null;
+ if (selection instanceof InlineFragment) {
+ InlineFragment inline = (InlineFragment) selection;
+ this.name = inline.getTypeCondition().getName();
+ this.isInline = true;
+ selectionSet = inline.getSelectionSet();
+ }
+ if (selection instanceof Field) {
+ Field subField = (Field) selection;
+ this.name = subField.getName();
+ selectionSet = subField.getSelectionSet();
+ }
+ if (selectionSet != null) {
+ selectionSet.getSelections().forEach(s -> {
+ SelectedFieldWrapper wrappedField = new SelectedFieldWrapper(s);
+ subFieldMap.put(wrappedField.getName(), wrappedField);
+ });
+ }
+ subFields = subFieldMap.values().stream().collect(Collectors.toList());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public List<SelectedField> getSubSelectedFields() {
+ return subFields;
+ }
+
+ @Override
+ public SelectedField getSubSelectedField(String name) {
+ return subFieldMap.get(name);
+ }
+
+ @Override
+ public boolean hasSubSelectedFields(String... name) {
+ return Arrays.stream(name).anyMatch(subFieldMap::containsKey);
+ }
+
+ @Override
+ public boolean isInline() {
+ return isInline;
+ }
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/SelectionSetWrapper.java b/src/main/java/org/apache/sling/graphql/core/engine/SelectionSetWrapper.java
new file mode 100644
index 0000000..40f0bfd
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/engine/SelectionSetWrapper.java
@@ -0,0 +1,72 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.core.engine;
+
+import graphql.schema.DataFetchingFieldSelectionSet;
+import org.apache.sling.graphql.api.SelectedField;
+import org.apache.sling.graphql.api.SelectionSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implement a wrapper for GraphQL DataFetchingFieldSelectionSet.
+ */
+public class SelectionSetWrapper implements SelectionSet {
+
+ private List<SelectedField> fields = new ArrayList<>();
+
+ private Map<String, SelectedField> fieldsMap = new HashMap<>();
+
+ public SelectionSetWrapper(@Nullable DataFetchingFieldSelectionSet selectionSet) {
+ if (selectionSet != null) {
+ selectionSet.get().getSubFieldsList().forEach(s -> fields.add(new SelectedFieldWrapper(s.getSingleField())));
+ initFlatMap(fields, "");
+ }
+ }
+
+ private void initFlatMap(List<SelectedField> parentList, String qualifiedPath) {
+ parentList.forEach(s -> {
+ String qualifiedName = qualifiedPath + s.getName();
+ fieldsMap.put(qualifiedName, s);
+ initFlatMap(s.getSubSelectedFields(), qualifiedName + "/");
+ });
+ }
+
+ @Override
+ @NotNull
+ public List<SelectedField> getFields() {
+ return Collections.unmodifiableList(fields);
+ }
+
+ @Override
+ public boolean contains(String qualifiedName) {
+ return fieldsMap.containsKey(qualifiedName);
+ }
+
+ @Override
+ public SelectedField get(String qualifiedName) {
+ return fieldsMap.get(qualifiedName);
+ }
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
index 76488df..130c942 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
@@ -23,8 +23,11 @@ import java.util.Collections;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
+import java.util.Objects;
import org.apache.sling.graphql.api.SchemaProvider;
+import org.apache.sling.graphql.api.SelectionSet;
+import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingGraphQLException;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.api.engine.ValidationResult;
@@ -39,6 +42,7 @@ import org.apache.sling.graphql.core.mocks.TypeTestDTO;
import org.apache.sling.graphql.core.mocks.UnionTypeResolver;
import org.junit.Test;
import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
@@ -71,6 +75,7 @@ public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/static", new EchoDataFetcher(staticData));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/fortyTwo", new EchoDataFetcher(42));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/digest", new DigestDataFetcher());
+ TestUtil.registerSlingDataFetcher(context.bundleContext(), "combined/fetcher", new EchoDataFetcher(unionData));
}
@Test
@@ -188,4 +193,65 @@ public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
assertThat(json, hasJsonPath("$.data.unionFetcher.items[0].testingArgument", equalTo("1, 2, 3")));
assertThat(json, hasJsonPath("$.data.unionFetcher.items[1].path", equalTo(resource.getPath())));
}
+
+ @Test
+ public void selectionSetTest() throws Exception {
+ queryJSON("{ combinedFetcher { boolValue resourcePath aTest { boolValue test resourcePath } allTests { boolValue test resourcePath } items { ... on Test { testingArgument } ... on SlingResource { path }} } }");
+
+ // retrieve the service used
+ ServiceReference<?>[] serviceReferences = context.bundleContext().getServiceReferences(SlingDataFetcher.class.getName(), "(name=combined/fetcher)");
+ EchoDataFetcher echoDataFetcher = (EchoDataFetcher) context.bundleContext().getService(serviceReferences[0]);
+
+ // Access the computed SelectionSet
+ SelectionSet selectionSet = echoDataFetcher.getSelectionSet();
+
+ // Assert it contains the expected results
+ String[] expectedQualifiedName = new String[] {
+ "boolValue",
+ "resourcePath",
+ "aTest",
+ "aTest/test",
+ "aTest/boolValue",
+ "aTest/resourcePath",
+ "allTests",
+ "allTests/test",
+ "allTests/boolValue",
+ "allTests/resourcePath",
+ "items",
+ "items/Test",
+ "items/Test/testingArgument",
+ "items/SlingResource",
+ "items/SlingResource/path"
+ };
+ for (String expectedQN : expectedQualifiedName) {
+ assertTrue(selectionSet.contains(expectedQN));
+ }
+
+ String[] expectedNonInlineQNs = new String[] {
+ "boolValue",
+ "resourcePath",
+ "aTest",
+ "aTest/test",
+ "aTest/boolValue",
+ "aTest/resourcePath",
+ "allTests",
+ "allTests/test",
+ "allTests/boolValue",
+ "allTests/resourcePath",
+ "items",
+ "items/Test/testingArgument",
+ "items/SlingResource/path"
+ };
+ for (String expectedNonInlineQN : expectedNonInlineQNs) {
+ assertFalse(Objects.requireNonNull(selectionSet.get(expectedNonInlineQN)).isInline());
+ }
+
+ String[] expectedInlineQNs = new String[] {
+ "items/Test",
+ "items/SlingResource"
+ };
+ for (String expectedInlineQN : expectedInlineQNs) {
+ assertTrue(Objects.requireNonNull(selectionSet.get(expectedInlineQN)).isInline());
+ }
+ }
}
diff --git a/src/test/java/org/apache/sling/graphql/core/mocks/EchoDataFetcher.java b/src/test/java/org/apache/sling/graphql/core/mocks/EchoDataFetcher.java
index 39d66e9..aed6424 100644
--- a/src/test/java/org/apache/sling/graphql/core/mocks/EchoDataFetcher.java
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/EchoDataFetcher.java
@@ -19,12 +19,14 @@
package org.apache.sling.graphql.core.mocks;
+import org.apache.sling.graphql.api.SelectionSet;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
public class EchoDataFetcher implements SlingDataFetcher<Object> {
private final Object data;
+ private SelectionSet selectionSet;
public EchoDataFetcher(Object data) {
this.data = data;
@@ -37,6 +39,11 @@ public class EchoDataFetcher implements SlingDataFetcher<Object> {
} else if(data == null) {
return e.getCurrentResource();
}
+ selectionSet = e.getSelectionSet();
return data;
}
+
+ public SelectionSet getSelectionSet() {
+ return selectionSet;
+ }
}
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/test-schema.txt
index e0853a8..9b1527d 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/test-schema.txt
@@ -38,6 +38,9 @@ type Query {
# Test union as return type
unionFetcher: Test2 @fetcher(name:"union/fetcher")
+
+ # Test combined as return type
+ combinedFetcher: Test3 @fetcher(name:"combined/fetcher")
}
union AllTypes @resolver(name:"union/resolver" source:"AllTypes") = SlingResource | Test
@@ -74,3 +77,10 @@ type Test2 {
items: [AllTypes]
}
+type Test3 {
+ boolValue: Boolean
+ resourcePath: String
+ aTest: Test
+ allTests: [Test]
+ items: [AllTypes]
+}