1313import java .util .Objects ;
1414import java .util .Optional ;
1515import java .util .Set ;
16+ import java .util .concurrent .ConcurrentHashMap ;
1617import java .util .function .Function ;
1718import org .slf4j .Logger ;
1819import org .slf4j .LoggerFactory ;
2324 * @author thced
2425 */
2526public record OpenApi (
26- String openapi , Info info , Collection <Server > servers , Map <String , PathItem > paths ) {
27+ String openapi ,
28+ Info info ,
29+ Collection <Server > servers ,
30+ Map <String , PathItem > paths ,
31+ Components components ) {
2732
2833 private static final Logger LOG = LoggerFactory .getLogger (OpenApi .class );
2934 private static final Set <String > SUPPORTED_VERSIONS = Set .of ("3.1.0" );
35+ private static final Map <String , Schema > SCHEMAS_CACHE = new ConcurrentHashMap <>();
3036
3137 public static OpenApi parse (Function <String , OpenApi > fn , String spec ) {
3238 return fn .apply (spec );
@@ -69,6 +75,20 @@ public Optional<Operation> getOperation(String method, String path) {
6975 .findFirst ();
7076 }
7177
78+ /**
79+ * Used to get access to the referenced schema components. It will strip off the
80+ * '#/components/schemas/' prefix and cache the found {@link Schema} instance.
81+ *
82+ * @param ref The "full" ref name
83+ * @return The found schema, or null
84+ */
85+ public Schema getResolvedSchema (String ref ) {
86+ String name = ref .replace ("#/components/schemas/" , "" );
87+ Schema found = SCHEMAS_CACHE .computeIfAbsent (name , components ::getSchema );
88+ LOG .debug ("Found resolved schema: {} -> {}" , ref , found );
89+ return found ;
90+ }
91+
7292 /**
7393 * The 'info' object.
7494 *
@@ -155,6 +175,7 @@ public record RequestBody(
155175 public record MediaType (Schema schema ) {}
156176
157177 public record Schema (
178+ String $ref ,
158179 String type ,
159180 String format ,
160181 Map <String , Object > properties ,
@@ -163,18 +184,35 @@ public record Schema(
163184 Number maximum ,
164185 Number minimum ) {
165186
187+ /**
188+ * If Schema has a $ref, we do not set any properties. The properties will be resolved later via
189+ * the referenced component {@link Components}.
190+ */
166191 public Schema {
167- if (type == null || type .isBlank ()) {
168- throw new LoadSpecificationException ("Type is missing" );
169- }
170- if (isNull (format ) && isNumber ()) {
171- format = "int32" ;
192+ if (isNull ($ref )) {
193+ if (type == null || type .isBlank ()) {
194+ throw new LoadSpecificationException ("Type is missing" );
195+ }
196+ if (isNull (format ) && isNumber ()) {
197+ format = "int32" ;
198+ }
199+ required = Objects .requireNonNullElse (required , List .of ());
200+ items = Objects .requireNonNullElse (items , Map .of ());
201+ properties = Objects .requireNonNullElse (properties , Map .of ());
202+ maximum = Objects .requireNonNullElse (maximum , Double .MAX_VALUE );
203+ minimum = Objects .requireNonNullElse (minimum , Double .MIN_VALUE );
172204 }
173- required = Objects .requireNonNullElse (required , List .of ());
174- items = Objects .requireNonNullElse (items , Map .of ());
175- properties = Objects .requireNonNullElse (properties , Map .of ());
176- maximum = Objects .requireNonNullElse (maximum , Double .MAX_VALUE );
177- minimum = Objects .requireNonNullElse (minimum , Double .MIN_VALUE );
205+ }
206+
207+ public Schema (
208+ String type ,
209+ String format ,
210+ Map <String , Object > properties ,
211+ Map <String , Object > items ,
212+ List <String > required ,
213+ Number maximum ,
214+ Number minimum ) {
215+ this (null , type , format , properties , items , required , maximum , minimum );
178216 }
179217
180218 public boolean isString () {
@@ -205,4 +243,10 @@ public boolean isArray() {
205243 return "array" .equalsIgnoreCase (type );
206244 }
207245 }
246+
247+ public record Components (Map <String , Schema > schemas ) {
248+ public Schema getSchema (String name ) {
249+ return schemas .get (name );
250+ }
251+ }
208252}
0 commit comments