You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2026/10/20 20:31:45 UTC

(solr) branch main updated: SOLR-16396: Align v2 full-config section keys with pluralised forms (#5175)

This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new d5a17012b2c SOLR-16396: Align v2 full-config section keys with pluralised forms (#5175)
d5a17012b2c is described below

commit d5a17012b2cf9e3ba903693e57b14fb6933aa1c7
Author: Jalaz Kumar <ja...@gmail.com>
AuthorDate: Wed Oct 21 02:01:38 2026 +0530

    SOLR-16396: Align v2 full-config section keys with pluralised forms (#5175)
    
    Co-authored-by: Eric Pugh <ep...@opensourceconnections.com>
    Co-authored-by: Claude Sonnet 5 <no...@anthropic.com>
---
 .../org/apache/solr/handler/SolrConfigHandler.java |  4 +-
 .../apache/solr/handler/admin/api/GetConfig.java   | 46 +++++++++++++++++++++-
 .../apache/solr/core/TestSolrConfigHandler.java    | 33 +++++++++++-----
 .../handler/admin/api/GetConfigSolrCloudTest.java  |  3 +-
 .../solr/handler/admin/api/GetConfigTest.java      |  8 +++-
 .../configuration-guide/pages/config-api.adoc      |  8 +++-
 6 files changed, 86 insertions(+), 16 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 2b2080a0673..341ecc2f096 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -190,7 +190,9 @@ public class SolrConfigHandler extends RequestHandlerBase
       if (parts.size() == 1) {
         // this is the whole config. sent out the whole payload
         V2ApiUtils.squashIntoSolrResponseWithoutHeader(
-            resp, new GetConfig(req).getConfig(req.getParams().getBool("expandParams", false)));
+            resp,
+            new GetConfig(req)
+                .getConfigWithInternalNames(req.getParams().getBool("expandParams", false)));
       } else {
         if (ConfigOverlay.NAME.equals(parts.get(1))) {
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(resp, new GetConfig(req).getOverlay());
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
index b8efea0a11c..5978cb8e0f2 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetConfig.java
@@ -24,6 +24,7 @@ import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_P
 
 import jakarta.inject.Inject;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -70,13 +71,25 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get {
 
   public static final String REQUEST_HANDLERS = "requestHandlers";
 
-  // v2-only renames: external (URL/response-key) component name -> internal SolrConfig section
-  // name. v1's GET /config/{component} keeps using the internal names unchanged.
+  // v2-only renames for /config/{component}(/{componentName}): external name -> internal
+  // SolrConfig section name. Excludes requestHandler, which has its own dedicated endpoint instead
+  // of routing through these paths.
   private static final Map<String, String> EXTERNAL_TO_INTERNAL_COMPONENT_NAME =
       Map.of(
           "searchComponents", "searchComponent",
           "queryResponseWriters", "queryResponseWriter");
 
+  // Same, but for the full /config dump's top-level keys, which also renames requestHandler.
+  // Derived from the map above so the two can't drift apart.
+  private static final Map<String, String> EXTERNAL_TO_INTERNAL_FULL_CONFIG_NAME =
+      buildFullConfigNameMap();
+
+  private static Map<String, String> buildFullConfigNameMap() {
+    Map<String, String> map = new HashMap<>(EXTERNAL_TO_INTERNAL_COMPONENT_NAME);
+    map.put(REQUEST_HANDLERS, SolrRequestHandler.TYPE);
+    return Map.copyOf(map);
+  }
+
   private static String toInternalComponentName(String externalName) {
     return EXTERNAL_TO_INTERNAL_COMPONENT_NAME.getOrDefault(externalName, externalName);
   }
@@ -90,6 +103,15 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get {
     return internalName;
   }
 
+  private static String toExternalFullConfigName(String internalName) {
+    for (Map.Entry<String, String> e : EXTERNAL_TO_INTERNAL_FULL_CONFIG_NAME.entrySet()) {
+      if (e.getValue().equals(internalName)) {
+        return e.getKey();
+      }
+    }
+    return internalName;
+  }
+
   private final SolrQueryRequest solrQueryRequest;
 
   @Inject
@@ -100,11 +122,31 @@ public class GetConfig extends JerseyResource implements ConfigApi.Get {
   @Override
   @PermissionName(CONFIG_READ_PERM)
   public ConfigInfoResponse getConfig(boolean expandParams) {
+    final var response = instantiateJerseyResponse(ConfigInfoResponse.class);
+    response.config = renameToExternalFullConfigNames(buildConfigMap(expandParams));
+    return response;
+  }
+
+  /**
+   * Same as {@link #getConfig(boolean)}, but keeps internal section names (e.g. {@code
+   * searchComponent}, {@code requestHandler}) instead of the v2-only pluralized forms. Used by
+   * {@link SolrConfigHandler}'s legacy v1 {@code GET /config}, which must preserve the original
+   * names for backward compatibility.
+   */
+  public ConfigInfoResponse getConfigWithInternalNames(boolean expandParams) {
     final var response = instantiateJerseyResponse(ConfigInfoResponse.class);
     response.config = buildConfigMap(expandParams);
     return response;
   }
 
+  private Map<String, Object> renameToExternalFullConfigNames(Map<String, Object> configMap) {
+    Map<String, Object> reMappedConfigMap = new NullKeyTolerantMap("children");
+    for (Map.Entry<String, Object> e : configMap.entrySet()) {
+      reMappedConfigMap.put(toExternalFullConfigName(e.getKey()), e.getValue());
+    }
+    return reMappedConfigMap;
+  }
+
   @Override
   @PermissionName(CONFIG_READ_PERM)
   public ConfigOverlayResponse getOverlay() {
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index 4cb5b8889b6..1f16c3ab7ff 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -98,13 +98,24 @@ public class TestSolrConfigHandler extends RestTestBase {
     solrTestRule.reset();
   }
 
+  /**
+   * The full "/config" response pluralizes some section names (e.g. searchComponent ->
+   * searchComponents, requestHandler -> requestHandlers) under the V2 API (see
+   * GetConfig#getConfig), but not under V1.
+   */
+  private static String fullConfigKey(
+      RestTestHarness harness, String internalName, String externalName) {
+    return harness.getBaseURL().contains("/____v2/") ? externalName : internalName;
+  }
+
   public void testProperty() throws Exception {
     RestTestHarness harness = restTestHarness;
+    String requestHandlerKey = fullConfigKey(harness, "requestHandler", "requestHandlers");
     MapWriter confMap = getRespMap(harness, "/config");
-    assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/luke"), null));
-    assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/info"), null));
-    assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/file"), null));
-    assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/ping"), null));
+    assertNotNull(confMap._get(asList("config", requestHandlerKey, "/admin/luke"), null));
+    assertNotNull(confMap._get(asList("config", requestHandlerKey, "/admin/info"), null));
+    assertNotNull(confMap._get(asList("config", requestHandlerKey, "/admin/file"), null));
+    assertNotNull(confMap._get(asList("config", requestHandlerKey, "/admin/ping"), null));
 
     String payload =
         "{\n"
@@ -188,6 +199,10 @@ public class TestSolrConfigHandler extends RestTestBase {
   }
 
   public static void reqhandlertests(RestTestHarness writeHarness) throws Exception {
+    // The per-component endpoints used elsewhere in this test are unaffected by the full-config
+    // pluralization below, since they already use the pluralized name.
+    String searchComponentKey = fullConfigKey(writeHarness, "searchComponent", "searchComponents");
+    String requestHandlerKey = fullConfigKey(writeHarness, "requestHandler", "requestHandlers");
     String payload =
         "{\n"
             + "'create-requesthandler' : { 'name' : '/x', 'class': 'org.apache.solr.handler.DumpRequestHandler' , 'startup' : 'lazy'}\n"
@@ -332,7 +347,7 @@ public class TestSolrConfigHandler extends RestTestBase {
     testForResponseElement(
         writeHarness,
         "/config",
-        asList("config", "searchComponent", "tc", "class"),
+        asList("config", searchComponentKey, "tc", "class"),
         "org.apache.solr.handler.component.TermsComponent",
         TIMEOUT_S);
     payload =
@@ -343,14 +358,14 @@ public class TestSolrConfigHandler extends RestTestBase {
     testForResponseElement(
         writeHarness,
         "/config",
-        asList("config", "searchComponent", "tc", "class"),
+        asList("config", searchComponentKey, "tc", "class"),
         "org.apache.solr.handler.component.TermVectorComponent",
         TIMEOUT_S);
 
     payload = "{\n" + "'delete-searchcomponent' : 'tc'" + "}";
     runConfigCommand(writeHarness, "/config", payload);
     testForResponseElement(
-        writeHarness, "/config", asList("config", "searchComponent", "tc"), null, TIMEOUT_S);
+        writeHarness, "/config", asList("config", searchComponentKey, "tc"), null, TIMEOUT_S);
     // <valueSourceParser name="countUsage"
     // class="org.apache.solr.core.CountUsageValueSourceParser"/>
     payload =
@@ -444,7 +459,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         testForResponseElement(
             writeHarness,
             "/config",
-            asList("config", "searchComponent", "myspellcheck", "spellchecker", "class"),
+            asList("config", searchComponentKey, "myspellcheck", "spellchecker", "class"),
             "solr.DirectSolrSpellChecker",
             TIMEOUT_S);
 
@@ -463,7 +478,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         testForResponseElement(
             writeHarness,
             "/config",
-            asList("config", "requestHandler", "/dump100", "class"),
+            asList("config", requestHandlerKey, "/dump100", "class"),
             "org.apache.solr.handler.DumpRequestHandler",
             TIMEOUT_S);
 
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java
index 20dbd09d1a6..8ae8b63afbf 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java
@@ -58,7 +58,8 @@ public class GetConfigSolrCloudTest extends SolrCloudTestCase {
     assertTrue(response.config.containsKey("luceneMatchVersion"));
     assertTrue(response.config.containsKey("updateHandler"));
     assertTrue(response.config.containsKey("query"));
-    assertTrue(response.config.containsKey("requestHandler"));
+    assertFalse(response.config.containsKey("requestHandler"));
+    assertTrue(response.config.containsKey("requestHandlers"));
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
index 4407845d155..c9c836405f8 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigTest.java
@@ -55,8 +55,12 @@ public class GetConfigTest extends SolrTestCase {
     assertTrue(response.config.containsKey("luceneMatchVersion"));
     assertTrue(response.config.containsKey("updateHandler"));
     assertTrue(response.config.containsKey("query"));
-    assertTrue(response.config.containsKey("requestHandler"));
-    assertTrue(response.config.containsKey("searchComponent"));
+    assertFalse(response.config.containsKey("requestHandler"));
+    assertTrue(response.config.containsKey("requestHandlers"));
+
+    assertFalse(response.config.containsKey("searchComponent"));
+    assertTrue(response.config.containsKey("searchComponents"));
+
     assertTrue(response.config.containsKey("updateProcessor"));
   }
 
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
index 01c043ae6a6..cbf7836a4f2 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/config-api.adoc
@@ -74,7 +74,9 @@ V2 API::
 http://localhost:8983/api/collections/techproducts/config
 ----
 
-The response is the same merged configuration as the V1 API, with one difference: a plugin's unnamed child elements are grouped under a self-describing `children` key instead of an empty string, since Solr's V2 JSON serialization library does not allow empty-string keys:
+The response is the same merged configuration as the V1 API, with three differences.
+
+First, a plugin's unnamed child elements are grouped under a self-describing `children` key instead of an empty string, since Solr's V2 JSON serialization library does not allow empty-string keys:
 
 [source,json]
 ----
@@ -87,6 +89,10 @@ The response is the same merged configuration as the V1 API, with one difference
     ]
 }]
 ----
+
+Second, the `searchComponent` and `queryResponseWriter` sections are renamed to their pluralized forms, `searchComponents` and `queryResponseWriters`, as described below.
+
+Third, the `requestHandler` section is similarly renamed to `requestHandlers`, matching the dedicated `/config/requestHandlers` endpoint described below.
 ====
 ======