This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit b5f148ad7bb4547b4257053cb8035c621413bf50 Author: Jan Høydahl <[email protected]> AuthorDate: Wed Oct 7 14:39:27 2026 +0200 SOLR-18504: Merge duplicate OpenAPI path templates (#5024) (cherry picked from commit a2f43aadfd212f6d9f05cdcdb5bd27b11fac81b1) --- .../SOLR-18504-duplicate-path-templates.yml | 10 +++++++++ solr/api/build.gradle | 24 ++++++++++++++++++++++ .../client/api/endpoint/ClusterFileStoreApis.java | 4 ++-- .../solr/client/api/endpoint/GetSchemaApi.java | 5 +++-- .../solr/handler/admin/api/GetSchemaField.java | 10 ++++----- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/changelog/unreleased/SOLR-18504-duplicate-path-templates.yml b/changelog/unreleased/SOLR-18504-duplicate-path-templates.yml new file mode 100644 index 00000000000..1e93ea2bcd9 --- /dev/null +++ b/changelog/unreleased/SOLR-18504-duplicate-path-templates.yml @@ -0,0 +1,10 @@ +title: > + The OpenAPI spec no longer describes the same URL twice: the dynamic-field and filestore-file + endpoints each named their path parameter differently per HTTP method, so consumers of the spec + saw two colliding entries, each missing the other's operations. +type: fixed +authors: + - name: Jan Høydahl +links: + - name: SOLR-18504 + url: https://issues.apache.org/jira/browse/SOLR-18504 diff --git a/solr/api/build.gradle b/solr/api/build.gradle index fb42df27a30..58386b103f2 100644 --- a/solr/api/build.gradle +++ b/solr/api/build.gradle @@ -79,6 +79,30 @@ dependencies { swaggerBuild libs.swagger3.jaxrs2.jakarta } +// OpenAPI treats templated paths that differ only in parameter name as the same path, so +// such a collision silently hides operations from consumers of the spec. +tasks.register('validateOpenApiSpec') { + dependsOn resolve + def specFile = file(project.openApiSpecFile) + def marker = layout.buildDirectory.file("openapi-spec-validated.txt").get().asFile + inputs.file(specFile) + outputs.file(marker) + doLast { + def collisions = new groovy.json.JsonSlurper().parse(specFile).paths.keySet() + .groupBy { it.replaceAll(/\{[^}]*\}/, '{}') } + .findAll { it.value.size() > 1 } + if (!collisions.isEmpty()) { + throw new GradleException( + "OpenAPI spec declares paths that differ only in path-parameter name:\n" + + collisions.values().flatten().collect { " " + it }.join("\n") + "\n" + + "Make the @PathParam names agree so the operations merge into a single path.") + } + marker.text = "ok" + } +} + +check.dependsOn validateOpenApiSpec + // Non-Java client generation tasks below: task buildJSClient(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask) { generatorName.set("javascript") diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java index e8dafdd4498..9effa63f791 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java @@ -87,10 +87,10 @@ public interface ClusterFileStoreApis { @Operation( summary = "Delete a file or directory from the filestore.", tags = {"file-store"}) - @Path("/files{path:.+}") + @Path("/files{filePath:.+}") SolrJerseyResponse deleteFile( @Parameter(description = "Path to a file or directory within the filestore") - @PathParam("path") + @PathParam("filePath") String path, @Parameter( description = diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetSchemaApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetSchemaApi.java index 119bf9d19ce..a42f832b154 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetSchemaApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetSchemaApi.java @@ -78,12 +78,13 @@ public interface GetSchemaApi { SchemaListDynamicFieldsResponse listDynamicFields(); @GET - @Path("/dynamicfields/{fieldName}") + @Path("/dynamicfields/{dynamicFieldName}") @StoreApiParameters @Operation( summary = "Get detailed info about a single dynamic field", tags = {"schema"}) - SchemaGetDynamicFieldInfoResponse getDynamicFieldInfo(@PathParam("fieldName") String fieldName); + SchemaGetDynamicFieldInfoResponse getDynamicFieldInfo( + @PathParam("dynamicFieldName") String dynamicFieldName); @GET @Path("/fieldtypes") diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaField.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaField.java index 70fc868bee5..e19b08fc325 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaField.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaField.java @@ -49,7 +49,7 @@ import org.apache.solr.security.PermissionNameProvider; * <li>/fields/{fieldName} * <li>/copyfields * <li>/dynamicfields - * <li>/dynamicfields/{fieldName} + * <li>/dynamicfields/{dynamicFieldName} * <li>/fieldtypes * <li>/fieldtypes/{fieldTypeName} * </ul> @@ -122,8 +122,8 @@ public class GetSchemaField extends JerseyResource implements GetSchemaApi.Field @Override @PermissionName(PermissionNameProvider.Name.SCHEMA_READ_PERM) - public SchemaGetDynamicFieldInfoResponse getDynamicFieldInfo(String fieldName) { - if (fieldName == null) { + public SchemaGetDynamicFieldInfoResponse getDynamicFieldInfo(String dynamicFieldName) { + if (dynamicFieldName == null) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Dynamic field name must not be null"); } @@ -132,13 +132,13 @@ public class GetSchemaField extends JerseyResource implements GetSchemaApi.Field final String realName = "dynamicfields"; SimpleOrderedMap<Object> dynamicFieldInfo = - retrieveFieldInfoOfType(realName, fieldName, params); + retrieveFieldInfoOfType(realName, dynamicFieldName, params); if (dynamicFieldInfo != null) { response.dynamicFieldInfo = dynamicFieldInfo; return response; } throw new SolrException( - SolrException.ErrorCode.NOT_FOUND, "No such dynamic field [" + fieldName + "]"); + SolrException.ErrorCode.NOT_FOUND, "No such dynamic field [" + dynamicFieldName + "]"); } @Override
