oscerd commented on code in PR #28183: URL: https://github.com/apache/camel/pull/28183#discussion_r4377365319
########## components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/OpenApiExternalReferences.java: ########## @@ -0,0 +1,315 @@ +/* + * 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.camel.component.rest.openapi; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.v3.parser.models.RefFormat; +import io.swagger.v3.parser.processors.ExternalRefProcessor; +import io.swagger.v3.parser.reference.ReferenceUtils; +import io.swagger.v3.parser.util.ClasspathHelper; +import io.swagger.v3.parser.util.DeserializationUtils; +import io.swagger.v3.parser.util.RefUtils; + +/** + * Checks the references ({@code $ref}) of an OpenAPI specification against the {@code externalReferences} option before + * the parser resolves them. The parser loads every external reference it meets: from the working directory and the + * classpath, or over http(s) when the reference is a URL or the specification itself comes from a URL. + * <p> + * The check works on the tree the parser resolves (YAML aliases expanded), resolves each reference the way the parser + * does, that is against the document that contains it and, for OpenAPI 3.1, against the enclosing {@code $id}s, which + * the parser also carries into the documents it reaches through a reference, and reads the local documents it allows to + * check their references in turn. A document it cannot read fails the check. + * <p> + * camel-openapi-java has a copy of this class for the specification it serves as api-doc; keep the two in sync. + */ +final class OpenApiExternalReferences { + + static final String ALL = "all"; + static final String LOCAL = "local"; + static final String NONE = "none"; + + /** + * The URI the parser gives an OpenAPI 3.1 specification that has no location (OpenAPIDereferencer31). + */ + private static final String LOCAL_URI = "local"; + + private OpenApiExternalReferences() { + } + + /** + * The value of the option to apply: {@link #ALL} when none is set. + * + * @throws IllegalArgumentException for a value other than all, local or none + */ + static String policy(String externalReferences) { + if (externalReferences == null || externalReferences.isBlank()) { + return ALL; + } + String policy = externalReferences.trim().toLowerCase(Locale.ROOT); + if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) { + return policy; + } + throw new IllegalArgumentException( + "Unknown externalReferences value: " + externalReferences + ", expected all, local or none"); + } + + /** + * Checks every {@code $ref} of the specification, and of the local documents it references, against the option. + * + * @param externalReferences the option: all, local or none + * @param specification the specification, as JSON or YAML text + * @param specificationUri where the specification comes from, for the error message + * @param remote whether the specification comes from a URL: the parser then resolves its + * external references against that URL + * @throws IllegalArgumentException for the first reference the option does not allow, and for a referenced document + * that cannot be read + */ + static void check(String externalReferences, String specification, String specificationUri, boolean remote) { + String policy = policy(externalReferences); + if (ALL.equals(policy)) { + return; + } + new Check(policy, specificationUri, remote).document(specification, null, null, List.of()); + } + + private static boolean isHttp(String target) { + String lower = target.toLowerCase(Locale.ROOT); + return lower.startsWith("http:") || lower.startsWith("https:"); + } + + /** + * A document a reference leads to: where the reference is, and the OpenAPI 3.1 {@code $id}s the parser carries into + * the document, which decide what the references inside it resolve to. + */ + private static final class Target { + + private final String location; + private final String origin; + private final List<String> inheritedIds; + + Target(String location, String origin, List<String> inheritedIds) { + this.location = location; + this.origin = origin; + this.inheritedIds = inheritedIds; + } + + String key() { + return inheritedIds.isEmpty() ? location : location + " under " + inheritedIds; Review Comment: Fixed in bd86d735. The visited set is now keyed by the resolved location plus the effective base the references resolve against (the inherited `$id`s folded in the same way `OpenAPI31Traverser.traverseSchema` does), instead of the raw `inheritedIds` list. `Target.key()` is gone; the dedup is `location + " under " + <computed base>`. When a schema is reached again under its own `$id`, the effective base is unchanged, so the key repeats and the recursion ends. A document reached under a genuinely different base still produces a different key and is checked again, so the different-base case is not weakened. Applied to both copies. Regression coverage in both modules: - `localFinishesForASchemaThatReferencesItself` (camel-rest-openapi) / `finishesForASchemaThatReferencesItself` (camel-openapi-java): a schema with an `$id` that `$ref`s itself now loads successfully under `local` — your `node.yaml` reproduction. Both completed in well under a second (no OOM). - `localChecksADocumentReachedAgainUnderAnotherId` / `checksADocumentReachedAgainUnderAnotherId`: a shared document is reached first without an `$id` (its relative `owner.yaml` is the local sibling, allowed) and then below an `http` `$id` (its `owner.yaml` now resolves against the server) — the second visit is **not** skipped and is rejected, with `SERVER.verify(0, ...)` confirming zero HTTP requests. 213+2 camel-rest-openapi and 106+2 camel-openapi-java reference-policy tests pass locally. _Claude Code on behalf of oscerd_ -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
