Repository: victools/jsonschema-generator
Pull request #326 (2023-03-13 21:45:27)

feat: Swagger2Module should support @Schema.ref on a type
- mainly supporting `@Schema(ref = "...")` attribute annotated on a type (not just a member)
- in order to properly handle multiple schemas being generated with a single generator/configuration instance, now also supporting to `resetAfterSchemaGenerationFinished()` on various configuration aspects


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c230790..8384598 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,12 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### `jsonschema-generator`
 #### Added
 - include basic Java module descriptor (also for standard modules and maven plugin)
+- add possibility to reset various types of configuration aspects after a schema was generated, to enable re-using a generator instance even if it is stateful (i.e., behaves differently on subsequent invocations)
 
 #### Changed
 - treat `java.time.Period` as `{ "type": "string" }` when `Option.ADDITIONAL_FIXED_TYPES` is enabled
 - treat `java.time.LocalTime` and `java.time.OffsetTime` as `{ "format": "time" }` when `Option.ADDITIONAL_FIXED_TYPES` is enabled (instead of "date-time")
 - update jackson dependency from version `2.13.4.20221013` to `2.14.2` and replace usage of now deprecated methods
 
+### `jsonschema-module-swagger-2`
+#### Added
+- consider `@Schema(ref = "...")` attribute, when it is annotated on a type (and not just a member) except for the main type being targeted
+
 ### `jsonschema-maven-plugin`
 #### Fixed
 - regression: filtering of considered classes for schema generation stopped working (after migration to `classgraph` in 4.28.0)
diff --git a/jsonschema-generator/README.md b/jsonschema-generator/README.md
index ba17e88..78e6225 100644
--- a/jsonschema-generator/README.md
+++ b/jsonschema-generator/README.md
@@ -1,7 +1,7 @@
 # Java JSON Schema Generator
 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.victools/jsonschema-generator/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.victools/jsonschema-generator)
 
-Creating JSON Schema (Draft 6, Draft 7 or Draft 2019-09) from your Java classes utilising Jackson (inspired by JJSchema).
+Creating JSON Schema (Draft 6, Draft 7, Draft 2019-09 or Draft 2020-12) from your Java classes utilising Jackson (inspired by JJSchema).
 
 Topics covered in this document are:
 - [Usage](#usage)
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/ConfigFunction.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/ConfigFunction.java
index 9ee95dc..c6c768f 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/ConfigFunction.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/ConfigFunction.java
@@ -23,7 +23,7 @@ package com.github.victools.jsonschema.generator;
  * @param <R> type of the configuration result
  */
 @FunctionalInterface
-public interface ConfigFunction<S extends TypeScope, R> {
+public interface ConfigFunction<S extends TypeScope, R> extends StatefulConfig {
 
     /**
      * Applies this function to the given arguments.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomDefinitionProviderV2.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomDefinitionProviderV2.java
index f413d76..10690aa 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomDefinitionProviderV2.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomDefinitionProviderV2.java
@@ -21,7 +21,8 @@ import com.fasterxml.classmate.ResolvedType;
 /**
  * Provider of non-standard JSON schema definitions.
  */
-public interface CustomDefinitionProviderV2 {
+@FunctionalInterface
+public interface CustomDefinitionProviderV2 extends StatefulConfig {
 
     /**
      * Look-up the non-standard JSON schema definition for a given type. If it returns null, the next definition provider is expected to be applied.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomPropertyDefinitionProvider.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomPropertyDefinitionProvider.java
index efa3adb..a638235 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomPropertyDefinitionProvider.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/CustomPropertyDefinitionProvider.java
@@ -21,7 +21,8 @@ package com.github.victools.jsonschema.generator;
  *
  * @param <M> either field or method for which a custom definition may be provided
  */
-public interface CustomPropertyDefinitionProvider<M extends MemberScope<?, ?>> {
+@FunctionalInterface
+public interface CustomPropertyDefinitionProvider<M extends MemberScope<?, ?>> extends StatefulConfig {
 
     /**
      * Look-up the non-standard JSON schema definition for a given property. If it returns null, the next definition provider is applied.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/InstanceAttributeOverrideV2.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/InstanceAttributeOverrideV2.java
index 04d6c79..1446722 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/InstanceAttributeOverrideV2.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/InstanceAttributeOverrideV2.java
@@ -23,7 +23,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
  *
  * @param <M> type of the reference/context to modify
  */
-public interface InstanceAttributeOverrideV2<M extends MemberScope<?, ?>> {
+@FunctionalInterface
+public interface InstanceAttributeOverrideV2<M extends MemberScope<?, ?>> extends StatefulConfig {
 
     /**
      * Add/remove attributes on the given JSON Schema node – this is specifically intended for attributes relating to a particular instance.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaBuilder.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaBuilder.java
index 82cbcf6..3b39976 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaBuilder.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaBuilder.java
@@ -130,6 +130,7 @@ public class SchemaBuilder {
             this.schemaNodes.add(jsonSchemaResult);
         }
         this.performCleanup();
+        this.config.resetAfterSchemaGenerationFinished();
         return jsonSchemaResult;
     }
 
@@ -148,6 +149,8 @@ public class SchemaBuilder {
         ResolvedType resolvedTargetType = this.typeContext.resolve(targetType, typeParameters);
         ObjectNode node = this.generationContext.createDefinitionReference(resolvedTargetType);
         this.schemaNodes.add(node);
+
+        this.config.resetAfterSchemaGenerationFinished();
         return node;
     }
 
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
index d4afe85..c4f101d 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfig.java
@@ -30,7 +30,7 @@ import java.util.Map;
 /**
  * Default implementation of a schema generator's configuration.
  */
-public interface SchemaGeneratorConfig {
+public interface SchemaGeneratorConfig extends StatefulConfig {
 
     /**
      * Getter for the designated JSON Schema version.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
index d56ed6e..7e5ac0a 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorConfigPart.java
@@ -31,6 +31,7 @@ import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Generic collection of reflection based analysis for populating a JSON Schema from a certain kind of member.
@@ -386,4 +387,13 @@ public class SchemaGeneratorConfigPart<M extends MemberScope<?, ?>> extends Sche
     public SchemaGeneratorConfigPart<M> withArrayUniqueItemsResolver(ConfigFunction<M, Boolean> resolver) {
         return (SchemaGeneratorConfigPart<M>) super.withArrayUniqueItemsResolver(resolver);
     }
+
+    @Override
+    public void resetAfterSchemaGenerationFinished() {
+        super.resetAfterSchemaGenerationFinished();
+
+        Stream.of(this.customDefinitionProviders, this.instanceAttributeOverrides, this.nullableChecks,
+                this.targetTypeOverridesResolvers, this.propertyNameOverrideResolvers
+        ).flatMap(List::stream).forEach(StatefulConfig::resetAfterSchemaGenerationFinished);
+    }
 }
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
index bb3828d..1ef1008 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorGeneralConfigPart.java
@@ -27,7 +27,9 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiFunction;
+import java.util.stream.Stream;
 
 /**
  * Generic collection of reflection based analysis for populating a JSON Schema targeting a specific type in general.
@@ -290,4 +292,13 @@ public class SchemaGeneratorGeneralConfigPart extends SchemaGeneratorTypeConfigP
     public SchemaGeneratorGeneralConfigPart withArrayUniqueItemsResolver(ConfigFunction<TypeScope, Boolean> resolver) {
         return (SchemaGeneratorGeneralConfigPart) super.withArrayUniqueItemsResolver(resolver);
     }
+
+    @Override
+    public void resetAfterSchemaGenerationFinished() {
+        super.resetAfterSchemaGenerationFinished();
+
+        Optional.ofNullable(this.definitionNamingStrategy).ifPresent(StatefulConfig::resetAfterSchemaGenerationFinished);
+        Stream.of(this.customDefinitionProviders, this.subtypeResolvers, this.typeAttributeOverrides, this.idResolvers, this.anchorResolvers)
+                .flatMap(List::stream).forEach(StatefulConfig::resetAfterSchemaGenerationFinished);
+    }
 }
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
index 2b3904f..e21fa39 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SchemaGeneratorTypeConfigPart.java
@@ -28,13 +28,14 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiFunction;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Generic collection of reflection based analysis for populating a JSON Schema.
  *
  * @param <S> type of the scope/type representation to analyse
  */
-public class SchemaGeneratorTypeConfigPart<S extends TypeScope> {
+public class SchemaGeneratorTypeConfigPart<S extends TypeScope> implements StatefulConfig {
 
     /**
      * Helper function for invoking a given function with the provided inputs or returning null, if all functions return null themselves.
@@ -526,4 +527,14 @@ public class SchemaGeneratorTypeConfigPart<S extends TypeScope> {
     public Boolean resolveArrayUniqueItems(S scope) {
         return getFirstDefinedValue(this.arrayUniqueItemsResolvers, scope);
     }
+
+    @Override
+    public void resetAfterSchemaGenerationFinished() {
+        Stream.of(this.titleResolvers, this.descriptionResolvers, this.defaultResolvers, this.enumResolvers,
+                this.stringMinLengthResolvers, this.stringMaxLengthResolvers, this.stringFormatResolvers,
+                this.stringPatternResolvers, this.numberInclusiveMinimumResolvers, this.numberExclusiveMinimumResolvers,
+                this.numberInclusiveMaximumResolvers, this.numberExclusiveMaximumResolvers, this.numberMultipleOfResolvers,
+                this.arrayMinItemsResolvers, this.arrayMaxItemsResolvers, this.arrayUniqueItemsResolvers
+        ).flatMap(List::stream).forEach(StatefulConfig::resetAfterSchemaGenerationFinished);
+    }
 }
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/StatefulConfig.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/StatefulConfig.java
new file mode 100644
index 0000000..1dd75b3
--- /dev/null
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/StatefulConfig.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 VicTools.
+ *
+ * Licensed 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 com.github.victools.jsonschema.generator;
+
+/**
+ * Type of configuration (or an aspect of it), that may change during a schema generation and7or remember some kind of state.
+ */
+public interface StatefulConfig {
+
+    /**
+     * Method being invoked after the generation of a single "main" type's schema has been completed. This enables the same {@code SchemaGenerator}
+     * instance to be re-used for multiple subsequent executions, even if some aspect of the configuration remembers the original "main" type.
+     */
+    default void resetAfterSchemaGenerationFinished() {
+        // nothing to reset by default
+    }
+}
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SubtypeResolver.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SubtypeResolver.java
index d0a6cd7..6d94647 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SubtypeResolver.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/SubtypeResolver.java
@@ -27,7 +27,7 @@ import java.util.List;
  * but allowing the schema validator to ignore any sub-schemas after the first match was found.
  */
 @FunctionalInterface
-public interface SubtypeResolver {
+public interface SubtypeResolver extends StatefulConfig {
 
     /**
      * Look-up the subtypes for a given type, that should be listed independently.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeAttributeOverrideV2.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeAttributeOverrideV2.java
index 55485fb..261f0fe 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeAttributeOverrideV2.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeAttributeOverrideV2.java
@@ -21,7 +21,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 /**
  * Entry point for customising the JSON Schema attributes being collected for a type in general, i.e. the part that may be referenced multiple times.
  */
-public interface TypeAttributeOverrideV2 {
+@FunctionalInterface
+public interface TypeAttributeOverrideV2 extends StatefulConfig {
 
     /**
      * Add/remove attributes on the given JSON Schema node – this is specifically intended for attributes relating to the type in general.
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
index 9179acc..8f09c78 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/impl/SchemaGeneratorConfigImpl.java
@@ -84,6 +84,13 @@ public class SchemaGeneratorConfigImpl implements SchemaGeneratorConfig {
         this.methodConfigPart = methodConfigPart;
     }
 
+    @Override
+    public void resetAfterSchemaGenerationFinished() {
+        this.typesInGeneralConfigPart.resetAfterSchemaGenerationFinished();
+        this.fieldConfigPart.resetAfterSchemaGenerationFinished();
+        this.methodConfigPart.resetAfterSchemaGenerationFinished();
+    }
+
     /**
      * Whether a given option is currently enabled (either specifically or by default).
      *
diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/naming/SchemaDefinitionNamingStrategy.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/naming/SchemaDefinitionNamingStrategy.java
index f23857c..7f53bc1 100644
--- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/naming/SchemaDefinitionNamingStrategy.java
+++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/naming/SchemaDefinitionNamingStrategy.java
@@ -17,13 +17,14 @@
 package com.github.victools.jsonschema.generator.naming;
 
 import com.github.victools.jsonschema.generator.SchemaGenerationContext;
+import com.github.victools.jsonschema.generator.StatefulConfig;
 import com.github.victools.jsonschema.generator.impl.DefinitionKey;
 import java.util.Map;
 
 /**
  * Naming strategy for the keys in the "definitions"/"$defs" containing shared/reused subschemas.
  */
-public interface SchemaDefinitionNamingStrategy {
+public interface SchemaDefinitionNamingStrategy extends StatefulConfig {
 
     /**
      * Getter for the name/key in the "definitions"/"$defs" to represent the given {@link DefinitionKey}.
diff --git a/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/ExternalRefCustomDefinitionProvider.java b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/ExternalRefCustomDefinitionProvider.java
new file mode 100644
index 0000000..cbc317b
--- /dev/null
+++ b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/ExternalRefCustomDefinitionProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 VicTools.
+ *
+ * Licensed 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 com.github.victools.jsonschema.module.swagger2;
+
+import com.fasterxml.classmate.ResolvedType;
+import com.github.victools.jsonschema.generator.CustomDefinition;
+import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2;
+import com.github.victools.jsonschema.generator.SchemaGenerationContext;
+import com.github.victools.jsonschema.generator.SchemaKeyword;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Optional;
+
+/**
+ * Replace any type annotated with {@code @Schema(ref = "...")} with the specified reference value, unless it is the main schema being targeted.
+ */
+public class ExternalRefCustomDefinitionProvider implements CustomDefinitionProviderV2 {
+
+    /**
+     * Reference to the targeted type, for which a schema is being generated, that should not be replaced by a "ref".
+     */
+    private Class<?> mainType;
+
+    @Override
+    public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, SchemaGenerationContext context) {
+        Class<?> erasedType = javaType.getErasedType();
+        if (this.mainType == null) {
+            this.mainType = erasedType;
+        }
+        if (this.mainType == erasedType) {
+            return null;
+        }
+        return Optional.ofNullable(erasedType.getAnnotation(Schema.class))
+                .map(Schema::ref)
+                .filter(ref -> !ref.isEmpty())
+                .map(ref -> context.getGeneratorConfig().createObjectNode().put(context.getKeyword(SchemaKeyword.TAG_REF), ref))
+                .map(schema -> new CustomDefinition(schema, CustomDefinition.INLINE_DEFINITION, CustomDefinition.INCLUDING_ATTRIBUTES))
+                .orElse(null);
+    }
+
+    @Override
+    public void resetAfterSchemaGenerationFinished() {
+        this.mainType = null;
+    }
+}
diff --git a/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java
index fdab586..c76626b 100644
--- a/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java
+++ b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java
@@ -61,6 +61,7 @@ public class Swagger2Module implements Module {
         configPart.withDescriptionResolver(this.createTypePropertyResolver(Schema::description, description -> !description.isEmpty()));
         configPart.withTitleResolver(this.createTypePropertyResolver(Schema::title, title -> !title.isEmpty()));
 
+        configPart.withCustomDefinitionProvider(new ExternalRefCustomDefinitionProvider());
         configPart.withSubtypeResolver(new Swagger2SubtypeResolver());
         configPart.withDefinitionNamingStrategy(new Swagger2SchemaDefinitionNamingStrategy(configPart.getDefinitionNamingStrategy()));
     }
diff --git a/slate-docs/source/includes/_swagger-2-module.md b/slate-docs/source/includes/_swagger-2-module.md
index 68b232b..59e46e1 100644
--- a/slate-docs/source/includes/_swagger-2-module.md
+++ b/slate-docs/source/includes/_swagger-2-module.md
@@ -14,38 +14,39 @@ SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(Sc
 
 1. From `@Schema(description = …)` on types in general, derive `"description"`.
 2. From `@Schema(title = …)` on types in general, derive `"title"`.
-3. From `@Schema(subTypes = …)` on types in general, derive `"anyOf"` alternatives.
-4. From `@Schema(anyOf = …)` on types in general (as alternative to `subTypes`), derive `"anyOf"` alternatives.
-5. From `@Schema(name = …)` on types in general, derive the keys/names in `"definitions"`/`"$defs"`.
-6. From `@Schema(description = …)` on fields/methods, derive `"description"`.
-7. From `@Schema(title = …)` on fields/methods, derive `"title"`.
-8. From `@Schema(implementation = …)` on fields/methods, override represented type.
-9. From `@Schema(hidden = true)` on fields/methods, skip certain properties.
-10. From `@Schema(name = …)` on fields/methods, override property names.
-11. From `@Schema(ref = …)` on fields/methods, replace subschema with `"$ref"` to external/separate schema.
-12. From `@Schema(allOf = …)` on fields/methods, include `"allOf"` parts.
-13. From `@Schema(anyOf = …)` on fields/methods, include `"anyOf"` parts.
-14. From `@Schema(oneOf = …)` on fields/methods, include `"oneOf"` parts.
-15. From `@Schema(not = …)` on fields/methods, include the indicated `"not"` subschema.
-16. From `@Schema(required = true)` on fields/methods, mark property as `"required"` in the schema containing the property.
-17. From `@Schema(requiredProperties = …)` on fields/methods, derive its `"required"` properties.
-18. From `@Schema(minProperties = …)` on fields/methods, derive its `"minProperties"`.
-19. From `@Schema(maxProperties = …)` on fields/methods, derive its `"maxProperties"`.
-20. From `@Schema(nullable = true)` on fields/methods, include `null` in its `"type"`.
-21. From `@Schema(allowableValues = …)` on fields/methods, derive its `"const"`/`"enum"`.
-22. From `@Schema(defaultValue = …)` on fields/methods, derive its `"default"`.
-23. From `@Schema(accessMode = AccessMode.READ_ONLY)` on fields/methods, to mark them as `"readOnly"`.
-24. From `@Schema(accessMode = AccessMode.WRITE_ONLY)` on fields/methods, to mark them as `"writeOnly"`.
-25. From `@Schema(minLength = …)` on fields/methods, derive its `"minLength"`.
-26. From `@Schema(maxLength = …)` on fields/methods, derive its `"maxLength"`.
-27. From `@Schema(format = …)` on fields/methods, derive its `"format"`.
-28. From `@Schema(pattern = …)` on fields/methods, derive its `"pattern"`.
-29. From `@Schema(multipleOf = …)` on fields/methods, derive its `"multipleOf"`.
-30. From `@Schema(minimum = …, exclusiveMinimum = …)` on fields/methods, derive its `"minimum"`/`"exclusiveMinimum"`.
-31. From `@Schema(maximum = …, exclusiveMaximum = …)` on fields/methods, derive its `"maximum"`/`"exclusiveMaximum"`.
-32. From `@ArraySchema(minItems = …)` on fields/methods, derive its `"minItems"`.
-33. From `@ArraySchema(maxItems = …)` on fields/methods, derive its `"maxItems"`.
-34. From `@ArraySchema(uniqueItems = …)` on fields/methods, derive its `"uniqueItems"`.
+3. From `@Schema(ref = …)` on types in general, replace subschema with `"$ref"` to external/separate schema (except for the main type being targeted).
+4. From `@Schema(subTypes = …)` on types in general, derive `"anyOf"` alternatives.
+5. From `@Schema(anyOf = …)` on types in general (as alternative to `subTypes`), derive `"anyOf"` alternatives.
+6. From `@Schema(name = …)` on types in general, derive the keys/names in `"definitions"`/`"$defs"`.
+7. From `@Schema(description = …)` on fields/methods, derive `"description"`.
+8. From `@Schema(title = …)` on fields/methods, derive `"title"`.
+9. From `@Schema(implementation = …)` on fields/methods, override represented type.
+10. From `@Schema(hidden = true)` on fields/methods, skip certain properties.
+11. From `@Schema(name = …)` on fields/methods, override property names.
+12. From `@Schema(ref = …)` on fields/methods, replace subschema with `"$ref"` to external/separate schema.
+13. From `@Schema(allOf = …)` on fields/methods, include `"allOf"` parts.
+14. From `@Schema(anyOf = …)` on fields/methods, include `"anyOf"` parts.
+15. From `@Schema(oneOf = …)` on fields/methods, include `"oneOf"` parts.
+16. From `@Schema(not = …)` on fields/methods, include the indicated `"not"` subschema.
+17. From `@Schema(required = true)` on fields/methods, mark property as `"required"` in the schema containing the property.
+18. From `@Schema(requiredProperties = …)` on fields/methods, derive its `"required"` properties.
+19. From `@Schema(minProperties = …)` on fields/methods, derive its `"minProperties"`.
+20. From `@Schema(maxProperties = …)` on fields/methods, derive its `"maxProperties"`.
+21. From `@Schema(nullable = true)` on fields/methods, include `null` in its `"type"`.
+22. From `@Schema(allowableValues = …)` on fields/methods, derive its `"const"`/`"enum"`.
+23. From `@Schema(defaultValue = …)` on fields/methods, derive its `"default"`.
+24. From `@Schema(accessMode = AccessMode.READ_ONLY)` on fields/methods, to mark them as `"readOnly"`.
+25. From `@Schema(accessMode = AccessMode.WRITE_ONLY)` on fields/methods, to mark them as `"writeOnly"`.
+26. From `@Schema(minLength = …)` on fields/methods, derive its `"minLength"`.
+27. From `@Schema(maxLength = …)` on fields/methods, derive its `"maxLength"`.
+28. From `@Schema(format = …)` on fields/methods, derive its `"format"`.
+29. From `@Schema(pattern = …)` on fields/methods, derive its `"pattern"`.
+30. From `@Schema(multipleOf = …)` on fields/methods, derive its `"multipleOf"`.
+31. From `@Schema(minimum = …, exclusiveMinimum = …)` on fields/methods, derive its `"minimum"`/`"exclusiveMinimum"`.
+32. From `@Schema(maximum = …, exclusiveMaximum = …)` on fields/methods, derive its `"maximum"`/`"exclusiveMaximum"`.
+33. From `@ArraySchema(minItems = …)` on fields/methods, derive its `"minItems"`.
+34. From `@ArraySchema(maxItems = …)` on fields/methods, derive its `"maxItems"`.
+35. From `@ArraySchema(uniqueItems = …)` on fields/methods, derive its `"uniqueItems"`.
 
 Schema attributes derived from `@Schema`/`@ArraySchema` on fields are also applied to their respective getter methods.
 Schema attributes derived from `@Schema`/`@ArraySchema` on getter methods are also applied to their associated fields.


Test patch:
diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaBuilderTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaBuilderTest.java
index fd885f0..8e76c9f 100644
--- a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaBuilderTest.java
+++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/SchemaBuilderTest.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.github.victools.jsonschema.generator.impl.TypeContextFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 
 /**
  * Test for the {@link SchemaBuilder}.
@@ -31,9 +32,9 @@ public class SchemaBuilderTest {
 
     @BeforeEach
     public void setUp() {
-        this.config = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON)
+        this.config = Mockito.spy(new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON)
                 .with(Option.PLAIN_DEFINITION_KEYS)
-                .build();
+                .build());
         this.typeContext = TypeContextFactory.createDefaultTypeContext();
     }
 
@@ -69,6 +70,7 @@ public class SchemaBuilderTest {
                 .set("schemas", instance.collectDefinitions("components/schemas"));
 
         TestUtils.assertGeneratedSchema(result, this.getClass(), "openapi.json");
+        Mockito.verify(this.config, Mockito.times(4)).resetAfterSchemaGenerationFinished();
     }
 
     private static class TestClass1 {
diff --git a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java
index 8f833a7..49fd230 100644
--- a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java
+++ b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java
@@ -37,24 +37,33 @@ import com.github.victools.jsonschema.module.swagger2.IntegrationTest.PersonRefe
 import io.swagger.v3.oas.annotations.media.ArraySchema;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Integration test of this module being used in a real SchemaGenerator instance.
  */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public class IntegrationTest {
 
-    @ParameterizedTest
-    @ValueSource(classes = {TestClass.class, Foo.class})
-    public void testIntegration(Class<?> rawTargetType) throws Exception {
+    private SchemaGenerator generator;
+
+    @BeforeAll
+    public void setUp() {
         Swagger2Module module = new Swagger2Module();
         SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON)
                 .with(Option.DEFINITIONS_FOR_ALL_OBJECTS, Option.NULLABLE_ARRAY_ITEMS_ALLOWED)
                 .with(Option.NONSTATIC_NONVOID_NONGETTER_METHODS, Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS)
                 .with(module);
-        SchemaGenerator generator = new SchemaGenerator(configBuilder.build());
-        JsonNode result = generator.generateSchema(rawTargetType);
+        this.generator = new SchemaGenerator(configBuilder.build());
+    }
+
+    @ParameterizedTest
+    @ValueSource(classes = {TestClass.class, Foo.class})
+    public void testIntegration(Class<?> rawTargetType) throws Exception {
+        JsonNode result = this.generator.generateSchema(rawTargetType);
 
         String rawJsonSchema = result.toString();
         JSONAssert.assertEquals('\n' + rawJsonSchema + '\n',
@@ -75,7 +84,7 @@ public class IntegrationTest {
 
     }
 
-    @Schema(minProperties = 2, maxProperties = 5, requiredProperties = {"fieldWithInclusiveNumericRange"})
+    @Schema(minProperties = 2, maxProperties = 5, requiredProperties = {"fieldWithInclusiveNumericRange"}, ref = "./TestClass-schema.json")
     static class TestClass {
 
         @Schema(hidden = true)
@@ -138,5 +147,7 @@ public class IntegrationTest {
 
         @Schema(oneOf = {Boolean.class, String.class})
         private Object oneOfBooleanOrString;
+
+        private TestClass test;
     }
 }
diff --git a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java
index 32e12a6..238073a 100644
--- a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java
+++ b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java
@@ -69,6 +69,7 @@ public class Swagger2ModuleTest {
 
         Mockito.verify(this.typesInGeneralConfigPart).withDescriptionResolver(Mockito.any());
         Mockito.verify(this.typesInGeneralConfigPart).withTitleResolver(Mockito.any());
+        Mockito.verify(this.typesInGeneralConfigPart).withCustomDefinitionProvider(Mockito.any(ExternalRefCustomDefinitionProvider.class));
         Mockito.verify(this.typesInGeneralConfigPart).withSubtypeResolver(Mockito.any(Swagger2SubtypeResolver.class));
         Mockito.verify(this.typesInGeneralConfigPart).getDefinitionNamingStrategy();
         Mockito.verify(this.typesInGeneralConfigPart).withDefinitionNamingStrategy(Mockito.any(Swagger2SchemaDefinitionNamingStrategy.class));
diff --git a/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json b/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json
index 8c9fd39..0032f35 100644
--- a/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json
+++ b/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json
@@ -36,6 +36,9 @@
         "person": {
             "$ref": "#/$defs/referenceToPerson",
             "writeOnly": true
+        },
+        "test": {
+            "$ref": "./TestClass-schema.json"
         }
     }
 }
\ No newline at end of file