Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a basic search API #93

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/FindJSONToolkit.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
if(NOT JSONToolkit_FOUND)
set(JSONTOOLKIT_INSTALL OFF CACHE BOOL "disable installation")
set(JSONTOOLKIT_JSONL OFF CACHE BOOL "disable JSONL support")
add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/jsontoolkit")
set(JSONToolkit_FOUND ON)
endif()
60 changes: 59 additions & 1 deletion src/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "configure.h"

#include <algorithm> // std::search
#include <cassert> // assert
#include <cctype> // std::tolower
#include <cstdint> // std::uint32_t, std::int64_t
Expand All @@ -17,7 +18,7 @@
#include <memory> // std::unique_ptr
#include <optional> // std::optional, std::nullopt
#include <sstream> // std::ostringstream
#include <string> // std::string
#include <string> // std::string, std::getline
#include <string_view> // std::string_view
#include <utility> // std::move

Expand Down Expand Up @@ -106,6 +107,52 @@ auto on_index(const sourcemeta::hydra::http::ServerLogger &,
sourcemeta::hydra::http::serve_file(
*(__global_data) / "generated" / "index.html", request, response);
}

auto on_search(const sourcemeta::hydra::http::ServerLogger &logger,
const sourcemeta::hydra::http::ServerRequest &request,
sourcemeta::hydra::http::ServerResponse &response) -> void {
const auto query{request.query("q")};
if (!query.has_value()) {
json_error(logger, request, response,
sourcemeta::hydra::http::Status::BAD_REQUEST, "missing-query",
"You must provide a query parameter to search for");
return;
}

auto result{sourcemeta::jsontoolkit::JSON::make_array()};
auto stream = sourcemeta::jsontoolkit::read_file(
*(__global_data) / "generated" / "search.jsonl");
stream.exceptions(std::ifstream::badbit);
// TODO: Extend the JSON Toolkit JSONL iterators to be able
// to access the stringified contents of the current entry
// BEFORE parsing it as JSON, letting the client decide
// whether to parse or not.
std::string line;
const auto &query_value{query.value()};
while (std::getline(stream, line)) {
if (std::search(line.cbegin(), line.cend(), query_value.cbegin(),
query_value.cend(), [](const auto left, const auto right) {
return std::tolower(left) == std::tolower(right);
}) == line.cend()) {
continue;
}

auto entry{sourcemeta::jsontoolkit::JSON::make_object()};
auto line_json{sourcemeta::jsontoolkit::parse(line)};
entry.assign("url", std::move(line_json.at(0)));
entry.assign("title", std::move(line_json.at(1)));
entry.assign("description", std::move(line_json.at(2)));
result.push_back(std::move(entry));

constexpr auto MAXIMUM_SEARCH_COUNT{10};
if (result.array_size() >= MAXIMUM_SEARCH_COUNT) {
break;
}
}

response.status(sourcemeta::hydra::http::Status::OK);
response.end(std::move(result));
}
#endif

static auto on_request(const sourcemeta::hydra::http::ServerLogger &logger,
Expand Down Expand Up @@ -189,6 +236,16 @@ static auto on_otherwise(const sourcemeta::hydra::http::ServerLogger &logger,
const sourcemeta::hydra::http::ServerRequest &request,
sourcemeta::hydra::http::ServerResponse &response)
-> void {
#ifdef SOURCEMETA_REGISTRY_ENTERPRISE
if (request.path() == "/search") {
json_error(logger, request, response,
sourcemeta::hydra::http::Status::METHOD_NOT_ALLOWED,
"method-not-allowed",
"This HTTP method is invalid for this URL");
return;
}
#endif

const auto maybe_schema{resolver(request_path_to_schema_uri(
configuration().at("url").to_string(), request.path()))};

Expand Down Expand Up @@ -250,6 +307,7 @@ auto main(int argc, char *argv[]) noexcept -> int {
sourcemeta::hydra::http::Server server;
#ifdef SOURCEMETA_REGISTRY_ENTERPRISE
server.route(sourcemeta::hydra::http::Method::GET, "/", on_index);
server.route(sourcemeta::hydra::http::Method::GET, "/search", on_search);
#endif
server.route(sourcemeta::hydra::http::Method::GET, "/*", on_request);
server.route(sourcemeta::hydra::http::Method::HEAD, "/*", on_request);
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/ce/search.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
GET {{base}}/search?q=foo
HTTP 404
Content-Type: application/json
[Captures]
current_request_id: header "X-Request-id"
[Asserts]
jsonpath "$.code" == 404
jsonpath "$.error" == "not-found"
jsonpath "$.message" == "There is no schema at this URL"
jsonpath "$.request" == "{{current_request_id}}"
21 changes: 0 additions & 21 deletions test/e2e/ee/no-meta.hurl

This file was deleted.

76 changes: 76 additions & 0 deletions test/e2e/ee/search.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
GET {{base}}/search
HTTP 400
Content-Type: application/json
[Captures]
current_request_id: header "X-Request-id"
[Asserts]
jsonpath "$.code" == 400
jsonpath "$.error" == "missing-query"
jsonpath "$.message" == "You must provide a query parameter to search for"
jsonpath "$.request" == "{{current_request_id}}"

GET {{base}}/search?q=
HTTP 400
Content-Type: application/json
[Captures]
current_request_id: header "X-Request-id"
[Asserts]
jsonpath "$.code" == 400
jsonpath "$.error" == "missing-query"
jsonpath "$.message" == "You must provide a query parameter to search for"
jsonpath "$.request" == "{{current_request_id}}"

POST {{base}}/search?q=foo
HTTP 405
Content-Type: application/json
[Captures]
current_request_id: header "X-Request-id"
[Asserts]
jsonpath "$.code" == 405
jsonpath "$.error" == "method-not-allowed"
jsonpath "$.message" == "This HTTP method is invalid for this URL"
jsonpath "$.request" == "{{current_request_id}}"

# A string we know won't give any results
GET {{base}}/search?q=xxxxxxxxxxxx
HTTP 200
[]

# Results with title/description
GET {{base}}/search?q=bundling
HTTP 200
[
{
"url": "/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example"
}
]

# Test casing
GET {{base}}/search?q=bUNdLing
HTTP 200
[
{
"url": "/example/bundling/single.json",
"title": "Bundling",
"description": "A bundling example"
}
]

# Results without title/description
GET {{base}}/search?q=camel
HTTP 200
[
{
"url": "/example/schemas/camelcase.json",
"title": "",
"description": ""
}
]

# No matter what, we impose a limit on the results
GET {{base}}/search?q=e
HTTP 200
[Asserts]
jsonpath "$" count <= 10
2 changes: 2 additions & 0 deletions test/sandbox/schemas/example/bundling/single.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/bundling/single.json",
"title": "Bundling",
"description": "A bundling example",
"properties": {
"foo": {
"$ref": "http://localhost:8000/example/v2.0/schema.json"
Expand Down
Loading