Explorar el Código

feat(indexes): facet values endpoint with counts

fszontagh hace 1 mes
padre
commit
262a68c383
Se han modificado 2 ficheros con 58 adiciones y 0 borrados
  1. 25 0
      src/handlers/indexes.cpp
  2. 33 0
      tests/test_api_integration.cpp

+ 25 - 0
src/handlers/indexes.cpp

@@ -1,3 +1,5 @@
+#include <cstdlib>
+
 #include "errors.hpp"
 #include "json_http.hpp"
 #include "server.hpp"
@@ -84,6 +86,29 @@ void registerIndexRoutes(ApiServer& s) {
         sendJson(res, 200, {{"indexes", out}});
     });
 
+    // Distinct values of an indexed field, with counts. Index-dependent by
+    // design: returns an empty list for an unindexed field and for array-valued
+    // fields, whose index keys are elements rather than values.
+    svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/indexes/([^/]+)/values)",
+            [d](const httplib::Request& req, httplib::Response& res) {
+        ApiKey k = requireKey(*d, req);
+        std::string project = req.matches[1], coll = req.matches[2], field = req.matches[3];
+        requireCapability(*d, k, req, project, coll, KeyOp::Read);
+
+        uint32_t limit = 100;
+        if (req.has_param("limit")) {
+            limit = (uint32_t)std::strtoul(req.get_param_value("limit").c_str(), nullptr, 10);
+            if (limit == 0 || limit > 1000)
+                throw ApiError(ErrCode::Unprocessable, "validation", "limit must be 1..1000");
+        }
+        const bool ascending = !(req.has_param("order") && req.get_param_value("order") == "desc");
+
+        auto vals = d->db.client().indexValues(qualify(project, coll), field, limit, ascending);
+        nlohmann::json out = nlohmann::json::array();
+        for (const auto& v : vals) out.push_back({{"value", v.value}, {"count", v.count}});
+        sendJson(res, 200, {{"values", out}});
+    });
+
     svr.Delete(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/indexes/([^/]+))",
                [d](const httplib::Request& req, httplib::Response& res) {
         ApiKey k = requireKey(*d, req);

+ 33 - 0
tests/test_api_integration.cpp

@@ -929,6 +929,39 @@ TEST_F(ApiFixture, IndexCreateRequiresField) {
     ASSERT_TRUE(bad); EXPECT_EQ(bad->status, 422);
 }
 
+TEST_F(ApiFixture, IndexValuesReturnsFacetCounts) {
+    auto c = admin();
+    std::string base = "/api/v1/projects/" + project_ + "/collections";
+    ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","facets"},{"kind","json"}}.dump(),
+                     "application/json")->status, 201);
+    std::string docs = base + "/facets/documents";
+    for (const char* st : {"open", "open", "closed"})
+        ASSERT_EQ(c.Post(docs.c_str(), nlohmann::json{{"status", st}}.dump(), "application/json")->status, 201);
+    ASSERT_EQ(c.Post((base + "/facets/indexes").c_str(),
+                     nlohmann::json{{"field","status"}}.dump(), "application/json")->status, 201);
+
+    auto r = c.Get((base + "/facets/indexes/status/values").c_str());
+    ASSERT_TRUE(r); EXPECT_EQ(r->status, 200);
+    auto body = nlohmann::json::parse(r->body);
+    uint64_t open = 0, closed = 0;
+    for (auto& e : body["values"]) {
+        if (e["value"] == "open")   open   = e["count"].get<uint64_t>();
+        if (e["value"] == "closed") closed = e["count"].get<uint64_t>();
+    }
+    EXPECT_EQ(open, 2u);
+    EXPECT_EQ(closed, 1u);
+}
+
+TEST_F(ApiFixture, IndexValuesEmptyForUnindexedField) {
+    auto c = admin();
+    std::string base = "/api/v1/projects/" + project_ + "/collections";
+    ASSERT_EQ(c.Post(base.c_str(), nlohmann::json{{"name","nofacet"},{"kind","json"}}.dump(),
+                     "application/json")->status, 201);
+    auto r = c.Get((base + "/nofacet/indexes/whatever/values").c_str());
+    ASSERT_TRUE(r); EXPECT_EQ(r->status, 200);
+    EXPECT_TRUE(nlohmann::json::parse(r->body)["values"].empty());
+}
+
 TEST_F(ApiFixture, UniqueIndexRejectsExistingDuplicates) {
     auto c = admin();
     std::string base = "/api/v1/projects/" + project_ + "/collections";