From 1107a14a2cf1e00cf4896dcb4fbaef202b2d1678 Mon Sep 17 00:00:00 2001 From: Mugr Rex Date: Mon, 24 Apr 2023 19:16:27 +0200 Subject: [PATCH] Removed nodejs Seems like my local build is ok, but in CI the references to engine are still there so I am reverting code in /nodejs to master --- include/nodejs/node_osrm_support.hpp | 10 +- src/nodejs/json_v8_renderer.hpp | 68 + src/nodejs/node_osrm.hpp | 32 + src/nodejs/node_osrm_support.hpp | 1706 ++++++++++++++++++++++++++ 4 files changed, 1811 insertions(+), 5 deletions(-) create mode 100644 src/nodejs/json_v8_renderer.hpp create mode 100644 src/nodejs/node_osrm.hpp create mode 100644 src/nodejs/node_osrm_support.hpp diff --git a/include/nodejs/node_osrm_support.hpp b/include/nodejs/node_osrm_support.hpp index 28f84263c..5296dc4ed 100644 --- a/include/nodejs/node_osrm_support.hpp +++ b/include/nodejs/node_osrm_support.hpp @@ -21,11 +21,11 @@ #include #include +#include #include #include #include -#include #include #include #include @@ -345,11 +345,11 @@ inline engine_config_ptr argumentsToEngineConfig(const Napi::CallbackInfo &args) return engine_config; } -inline std::optional> +inline boost::optional> parseCoordinateArray(const Napi::Array &coordinates_array) { Napi::HandleScope scope(coordinates_array.Env()); - std::optional> resulting_coordinates; + boost::optional> resulting_coordinates; std::vector temp_coordinates; for (uint32_t i = 0; i < coordinates_array.Length(); ++i) @@ -400,7 +400,7 @@ parseCoordinateArray(const Napi::Array &coordinates_array) osrm::util::FloatLatitude{std::move(lat)}); } - resulting_coordinates = std::make_optional(std::move(temp_coordinates)); + resulting_coordinates = boost::make_optional(std::move(temp_coordinates)); return resulting_coordinates; } @@ -968,7 +968,7 @@ inline bool parseCommonParameters(const Napi::Object &obj, ParamType ¶ms) inline PluginParameters argumentsToPluginParameters( const Napi::CallbackInfo &args, - const std::optional &output_format = {}) + const boost::optional &output_format = {}) { if (args.Length() < 3 || !args[1].IsObject()) { diff --git a/src/nodejs/json_v8_renderer.hpp b/src/nodejs/json_v8_renderer.hpp new file mode 100644 index 000000000..4b8bbd193 --- /dev/null +++ b/src/nodejs/json_v8_renderer.hpp @@ -0,0 +1,68 @@ +#ifndef OSRM_BINDINGS_NODE_JSON_V8_RENDERER_HPP +#define OSRM_BINDINGS_NODE_JSON_V8_RENDERER_HPP + +#include "osrm/json_container.hpp" +#include + +#include + +namespace node_osrm +{ + +struct V8Renderer +{ + explicit V8Renderer(const Napi::Env &env, Napi::Value &out) : env(env), out(out) {} + + void operator()(const osrm::json::String &string) const + { + out = Napi::String::New(env, string.value); + } + + void operator()(const osrm::json::Number &number) const + { + out = Napi::Number::New(env, number.value); + } + + void operator()(const osrm::json::Object &object) const + { + Napi::Object obj = Napi::Object::New(env); + for (const auto &keyValue : object.values) + { + Napi::Value child; + mapbox::util::apply_visitor(V8Renderer(env, child), keyValue.second); + obj.Set(keyValue.first, child); + } + out = obj; + } + + void operator()(const osrm::json::Array &array) const + { + Napi::Array a = Napi::Array::New(env, array.values.size()); + for (auto i = 0u; i < array.values.size(); ++i) + { + Napi::Value child; + mapbox::util::apply_visitor(V8Renderer(env, child), array.values[i]); + a.Set(i, child); + } + out = a; + } + + void operator()(const osrm::json::True &) const { out = Napi::Boolean::New(env, true); } + + void operator()(const osrm::json::False &) const { out = Napi::Boolean::New(env, false); } + + void operator()(const osrm::json::Null &) const { out = env.Null(); } + + private: + const Napi::Env &env; + Napi::Value &out; +}; + +inline void renderToV8(const Napi::Env &env, Napi::Value &out, const osrm::json::Object &object) +{ + V8Renderer renderer(env, out); + renderer(object); +} +} // namespace node_osrm + +#endif // JSON_V8_RENDERER_HPP diff --git a/src/nodejs/node_osrm.hpp b/src/nodejs/node_osrm.hpp new file mode 100644 index 000000000..f85fb5362 --- /dev/null +++ b/src/nodejs/node_osrm.hpp @@ -0,0 +1,32 @@ +#ifndef OSRM_BINDINGS_NODE_HPP +#define OSRM_BINDINGS_NODE_HPP + +#include "osrm/osrm_fwd.hpp" + +#include + +#include + +namespace node_osrm +{ + +class Engine final : public Napi::ObjectWrap +{ + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + Engine(const Napi::CallbackInfo &info); + + std::shared_ptr this_; + + private: + Napi::Value route(const Napi::CallbackInfo &info); + Napi::Value nearest(const Napi::CallbackInfo &info); + Napi::Value table(const Napi::CallbackInfo &info); + Napi::Value tile(const Napi::CallbackInfo &info); + Napi::Value match(const Napi::CallbackInfo &info); + Napi::Value trip(const Napi::CallbackInfo &info); +}; + +} // namespace node_osrm + +#endif diff --git a/src/nodejs/node_osrm_support.hpp b/src/nodejs/node_osrm_support.hpp new file mode 100644 index 000000000..5296dc4ed --- /dev/null +++ b/src/nodejs/node_osrm_support.hpp @@ -0,0 +1,1706 @@ +#ifndef OSRM_BINDINGS_NODE_SUPPORT_HPP +#define OSRM_BINDINGS_NODE_SUPPORT_HPP + +#include "nodejs/json_v8_renderer.hpp" +#include "engine/api/flatbuffers/fbresult_generated.h" +#include "osrm/approach.hpp" +#include "osrm/bearing.hpp" +#include "osrm/coordinate.hpp" +#include "osrm/engine_config.hpp" +#include "osrm/json_container.hpp" +#include "osrm/match_parameters.hpp" +#include "osrm/nearest_parameters.hpp" +#include "osrm/osrm.hpp" +#include "osrm/route_parameters.hpp" +#include "osrm/status.hpp" +#include "osrm/storage_config.hpp" +#include "osrm/table_parameters.hpp" +#include "osrm/tile_parameters.hpp" +#include "osrm/trip_parameters.hpp" +#include "util/json_renderer.hpp" +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace node_osrm +{ + +using engine_config_ptr = std::unique_ptr; +using route_parameters_ptr = std::unique_ptr; +using trip_parameters_ptr = std::unique_ptr; +using tile_parameters_ptr = std::unique_ptr; +using match_parameters_ptr = std::unique_ptr; +using nearest_parameters_ptr = std::unique_ptr; +using table_parameters_ptr = std::unique_ptr; + +struct PluginParameters +{ + bool renderToBuffer = false; +}; + +using ObjectOrString = typename mapbox::util::variant; + +template inline Napi::Value render(const Napi::Env &env, const ResultT &result); + +template <> Napi::Value inline render(const Napi::Env &env, const std::string &result) +{ + return Napi::Buffer::Copy(env, result.data(), result.size()); +} + +template <> Napi::Value inline render(const Napi::Env &env, const ObjectOrString &result) +{ + if (result.is()) + { + // Convert osrm::json object tree into matching v8 object tree + Napi::Value value; + renderToV8(env, value, result.get()); + return value; + } + else + { + // Return the string object as a node Buffer + return Napi::Buffer::Copy( + env, result.get().data(), result.get().size()); + } +} + +inline bool IsUnsignedInteger(const Napi::Value &value) +{ + if (!value.IsNumber()) + { + return false; + } + const auto doubleValue = value.ToNumber().DoubleValue(); + return doubleValue >= 0.0 && std::floor(doubleValue) == doubleValue; +} + +inline void ParseResult(const osrm::Status &result_status, osrm::json::Object &result) +{ + const auto code_iter = result.values.find("code"); + const auto end_iter = result.values.end(); + + BOOST_ASSERT(code_iter != end_iter); + + if (result_status == osrm::Status::Error) + { + throw std::logic_error(code_iter->second.get().value.c_str()); + } + + result.values.erase(code_iter); + const auto message_iter = result.values.find("message"); + if (message_iter != end_iter) + { + result.values.erase(message_iter); + } +} + +inline void ParseResult(const osrm::Status & /*result_status*/, const std::string & /*unused*/) {} +inline void ParseResult(const osrm::Status &result_status, + const flatbuffers::FlatBufferBuilder &fbs_builder) +{ + auto fbs_result = osrm::engine::api::fbresult::GetFBResult(fbs_builder.GetBufferPointer()); + + if (result_status == osrm::Status::Error) + { + BOOST_ASSERT(fbs_result->code()); + throw std::logic_error(fbs_result->code()->message()->c_str()); + } +} + +inline void ThrowError(const Napi::Env &env, const char *message) +{ + Napi::Error::New(env, message).ThrowAsJavaScriptException(); +} + +inline void ThrowTypeError(const Napi::Env &env, const char *message) +{ + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); +} + +inline engine_config_ptr argumentsToEngineConfig(const Napi::CallbackInfo &args) +{ + Napi::HandleScope scope(args.Env()); + auto engine_config = std::make_unique(); + + if (args.Length() == 0) + { + return engine_config; + } + else if (args.Length() > 1) + { + ThrowError(args.Env(), "Only accepts one parameter"); + return engine_config_ptr(); + } + + BOOST_ASSERT(args.Length() == 1); + + if (args[0].IsString()) + { + engine_config->storage_config = osrm::StorageConfig(args[0].ToString().Utf8Value()); + engine_config->use_shared_memory = false; + return engine_config; + } + else if (!args[0].IsObject()) + { + ThrowError(args.Env(), "Parameter must be a path or options object"); + return engine_config_ptr(); + } + + BOOST_ASSERT(args[0].IsObject()); + auto params = args[0].As(); + + auto path = params.Get("path"); + if (path.IsEmpty()) + return engine_config_ptr(); + + auto memory_file = params.Get("memory_file"); + if (memory_file.IsEmpty()) + return engine_config_ptr(); + + auto shared_memory = params.Get("shared_memory"); + if (shared_memory.IsEmpty()) + return engine_config_ptr(); + + auto mmap_memory = params.Get("mmap_memory"); + if (mmap_memory.IsEmpty()) + return engine_config_ptr(); + + if (!memory_file.IsUndefined()) + { + if (path.IsUndefined()) + { + ThrowError(args.Env(), "memory_file option requires a path to a file."); + return engine_config_ptr(); + } + + engine_config->memory_file = memory_file.ToString().Utf8Value(); + } + + auto dataset_name = params.Get("dataset_name"); + if (dataset_name.IsEmpty()) + return engine_config_ptr(); + if (!dataset_name.IsUndefined()) + { + if (dataset_name.IsString()) + { + engine_config->dataset_name = dataset_name.ToString().Utf8Value(); + } + else + { + ThrowError(args.Env(), "dataset_name needs to be a string"); + return engine_config_ptr(); + } + } + + if (!path.IsUndefined()) + { + engine_config->storage_config = osrm::StorageConfig(path.ToString().Utf8Value()); + + engine_config->use_shared_memory = false; + } + if (!shared_memory.IsUndefined()) + { + if (shared_memory.IsBoolean()) + { + engine_config->use_shared_memory = shared_memory.ToBoolean().Value(); + } + else + { + ThrowError(args.Env(), "Shared_memory option must be a boolean"); + return engine_config_ptr(); + } + } + if (!mmap_memory.IsUndefined()) + { + if (mmap_memory.IsBoolean()) + { + engine_config->use_mmap = mmap_memory.ToBoolean().Value(); + } + else + { + ThrowError(args.Env(), "mmap_memory option must be a boolean"); + return engine_config_ptr(); + } + } + + if (path.IsUndefined() && !engine_config->use_shared_memory) + { + ThrowError(args.Env(), + "Shared_memory must be enabled if no path is " + "specified"); + return engine_config_ptr(); + } + + auto algorithm = params.Get("algorithm"); + if (algorithm.IsEmpty()) + return engine_config_ptr(); + + if (algorithm.IsString()) + { + auto algorithm_str = algorithm.ToString().Utf8Value(); + if (algorithm_str == "CH") + { + engine_config->algorithm = osrm::EngineConfig::Algorithm::CH; + } + else if (algorithm_str == "CoreCH") + { + engine_config->algorithm = osrm::EngineConfig::Algorithm::CH; + } + else if (algorithm_str == "MLD") + { + engine_config->algorithm = osrm::EngineConfig::Algorithm::MLD; + } + else + { + ThrowError(args.Env(), "algorithm option must be one of 'CH', 'CoreCH', or 'MLD'."); + return engine_config_ptr(); + } + } + else if (!algorithm.IsUndefined()) + { + ThrowError(args.Env(), + "algorithm option must be a string and one of 'CH', 'CoreCH', or 'MLD'."); + return engine_config_ptr(); + } + + // Set EngineConfig system-wide limits on construction, if requested + + auto max_locations_trip = params.Get("max_locations_trip"); + auto max_locations_viaroute = params.Get("max_locations_viaroute"); + auto max_locations_distance_table = params.Get("max_locations_distance_table"); + auto max_locations_map_matching = params.Get("max_locations_map_matching"); + auto max_results_nearest = params.Get("max_results_nearest"); + auto max_alternatives = params.Get("max_alternatives"); + auto max_radius_map_matching = params.Get("max_radius_map_matching"); + auto default_radius = params.Get("default_radius"); + + if (!max_locations_trip.IsUndefined() && !max_locations_trip.IsNumber()) + { + ThrowError(args.Env(), "max_locations_trip must be an integral number"); + return engine_config_ptr(); + } + if (!max_locations_viaroute.IsUndefined() && !max_locations_viaroute.IsNumber()) + { + ThrowError(args.Env(), "max_locations_viaroute must be an integral number"); + return engine_config_ptr(); + } + if (!max_locations_distance_table.IsUndefined() && !max_locations_distance_table.IsNumber()) + { + ThrowError(args.Env(), "max_locations_distance_table must be an integral number"); + return engine_config_ptr(); + } + if (!max_locations_map_matching.IsUndefined() && !max_locations_map_matching.IsNumber()) + { + ThrowError(args.Env(), "max_locations_map_matching must be an integral number"); + return engine_config_ptr(); + } + if (!max_results_nearest.IsUndefined() && !max_results_nearest.IsNumber()) + { + ThrowError(args.Env(), "max_results_nearest must be an integral number"); + return engine_config_ptr(); + } + if (!max_alternatives.IsUndefined() && !max_alternatives.IsNumber()) + { + ThrowError(args.Env(), "max_alternatives must be an integral number"); + return engine_config_ptr(); + } + if (!default_radius.IsUndefined() && !default_radius.IsNumber()) + { + ThrowError(args.Env(), "default_radius must be an integral number"); + return engine_config_ptr(); + } + + if (max_locations_trip.IsNumber()) + engine_config->max_locations_trip = max_locations_trip.ToNumber().Int32Value(); + if (max_locations_viaroute.IsNumber()) + engine_config->max_locations_viaroute = max_locations_viaroute.ToNumber().Int32Value(); + if (max_locations_distance_table.IsNumber()) + engine_config->max_locations_distance_table = + max_locations_distance_table.ToNumber().Int32Value(); + if (max_locations_map_matching.IsNumber()) + engine_config->max_locations_map_matching = + max_locations_map_matching.ToNumber().Int32Value(); + if (max_results_nearest.IsNumber()) + engine_config->max_results_nearest = max_results_nearest.ToNumber().Int32Value(); + if (max_alternatives.IsNumber()) + engine_config->max_alternatives = max_alternatives.ToNumber().Int32Value(); + if (max_radius_map_matching.IsNumber()) + engine_config->max_radius_map_matching = max_radius_map_matching.ToNumber().DoubleValue(); + if (default_radius.IsNumber()) + engine_config->default_radius = default_radius.ToNumber().DoubleValue(); + + return engine_config; +} + +inline boost::optional> +parseCoordinateArray(const Napi::Array &coordinates_array) +{ + Napi::HandleScope scope(coordinates_array.Env()); + boost::optional> resulting_coordinates; + std::vector temp_coordinates; + + for (uint32_t i = 0; i < coordinates_array.Length(); ++i) + { + Napi::Value coordinate = coordinates_array.Get(i); + if (coordinate.IsEmpty()) + return resulting_coordinates; + + if (!coordinate.IsArray()) + { + ThrowError(coordinates_array.Env(), "Coordinates must be an array of (lon/lat) pairs"); + return resulting_coordinates; + } + + Napi::Array coordinate_pair = coordinate.As(); + if (coordinate_pair.Length() != 2) + { + ThrowError(coordinates_array.Env(), "Coordinates must be an array of (lon/lat) pairs"); + return resulting_coordinates; + } + + if (!coordinate_pair.Get(static_cast(0)).IsNumber() || + !coordinate_pair.Get(static_cast(1)).IsNumber()) + { + ThrowError(coordinates_array.Env(), + "Each member of a coordinate pair must be a number"); + return resulting_coordinates; + } + + double lon = coordinate_pair.Get(static_cast(0)).As().DoubleValue(); + double lat = coordinate_pair.Get(static_cast(1)).As().DoubleValue(); + + if (std::isnan(lon) || std::isnan(lat) || std::isinf(lon) || std::isinf(lat)) + { + ThrowError(coordinates_array.Env(), "Lng/Lat coordinates must be valid numbers"); + return resulting_coordinates; + } + + if (lon > 180 || lon < -180 || lat > 90 || lat < -90) + { + ThrowError(coordinates_array.Env(), + "Lng/Lat coordinates must be within world bounds " + "(-180 < lng < 180, -90 < lat < 90)"); + return resulting_coordinates; + } + + temp_coordinates.emplace_back(osrm::util::FloatLongitude{std::move(lon)}, + osrm::util::FloatLatitude{std::move(lat)}); + } + + resulting_coordinates = boost::make_optional(std::move(temp_coordinates)); + return resulting_coordinates; +} + +// Parses all the non-service specific parameters +template +inline bool argumentsToParameter(const Napi::CallbackInfo &args, + ParamType ¶ms, + bool requires_multiple_coordinates) +{ + Napi::HandleScope scope(args.Env()); + + if (args.Length() < 2) + { + ThrowTypeError(args.Env(), "Two arguments required"); + return false; + } + + if (!args[0].IsObject()) + { + ThrowTypeError(args.Env(), "First arg must be an object"); + return false; + } + + Napi::Object obj = args[0].As(); + + Napi::Value coordinates = obj.Get("coordinates"); + if (coordinates.IsEmpty()) + return false; + + if (coordinates.IsUndefined()) + { + ThrowError(args.Env(), "Must provide a coordinates property"); + return false; + } + else if (coordinates.IsArray()) + { + auto coordinates_array = coordinates.As(); + if (coordinates_array.Length() < 2 && requires_multiple_coordinates) + { + ThrowError(args.Env(), "At least two coordinates must be provided"); + return false; + } + else if (!requires_multiple_coordinates && coordinates_array.Length() != 1) + { + ThrowError(args.Env(), "Exactly one coordinate pair must be provided"); + return false; + } + auto maybe_coordinates = parseCoordinateArray(coordinates_array); + if (maybe_coordinates) + { + std::copy(maybe_coordinates->begin(), + maybe_coordinates->end(), + std::back_inserter(params->coordinates)); + } + else + { + return false; + } + } + else if (!coordinates.IsUndefined()) + { + BOOST_ASSERT(!coordinates.IsArray()); + ThrowError(args.Env(), "Coordinates must be an array of (lon/lat) pairs"); + return false; + } + + if (obj.Has("approaches")) + { + Napi::Value approaches = obj.Get("approaches"); + if (approaches.IsEmpty()) + return false; + + if (!approaches.IsArray()) + { + ThrowError(args.Env(), "Approaches must be an arrays of strings"); + return false; + } + + auto approaches_array = approaches.As(); + + if (approaches_array.Length() != params->coordinates.size()) + { + ThrowError(args.Env(), + "Approaches array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < approaches_array.Length(); ++i) + { + Napi::Value approach_raw = approaches_array.Get(i); + if (approach_raw.IsEmpty()) + return false; + + if (approach_raw.IsNull()) + { + params->approaches.emplace_back(); + } + else if (approach_raw.IsString()) + { + std::string approach_str = approach_raw.ToString().Utf8Value(); + if (approach_str == "curb") + { + params->approaches.push_back(osrm::Approach::CURB); + } + else if (approach_str == "unrestricted") + { + params->approaches.push_back(osrm::Approach::UNRESTRICTED); + } + else + { + ThrowError(args.Env(), + "'approaches' param must be one of [curb, unrestricted]"); + return false; + } + } + else + { + ThrowError(args.Env(), "Approach must be a string: [curb, unrestricted] or null"); + return false; + } + } + } + + if (obj.Has("bearings")) + { + Napi::Value bearings = obj.Get("bearings"); + if (bearings.IsEmpty()) + return false; + + if (!bearings.IsArray()) + { + ThrowError(args.Env(), "Bearings must be an array of arrays of numbers"); + return false; + } + + auto bearings_array = bearings.As(); + + if (bearings_array.Length() != params->coordinates.size()) + { + ThrowError(args.Env(), "Bearings array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < bearings_array.Length(); ++i) + { + Napi::Value bearing_raw = bearings_array.Get(i); + if (bearing_raw.IsEmpty()) + return false; + + if (bearing_raw.IsNull()) + { + params->bearings.emplace_back(); + } + else if (bearing_raw.IsArray()) + { + auto bearing_pair = bearing_raw.As(); + if (bearing_pair.Length() == 2) + { + if (!bearing_pair.Get(static_cast(0)).IsNumber() || + !bearing_pair.Get(static_cast(1)).IsNumber()) + { + ThrowError(args.Env(), "Bearing values need to be numbers in range 0..360"); + return false; + } + + const auto bearing = + bearing_pair.Get(static_cast(0)).ToNumber().Int32Value(); + const auto range = + bearing_pair.Get(static_cast(1)).ToNumber().Int32Value(); + + if (bearing < 0 || bearing > 360 || range < 0 || range > 180) + { + ThrowError(args.Env(), "Bearing values need to be in range 0..360, 0..180"); + return false; + } + + params->bearings.push_back( + osrm::Bearing{static_cast(bearing), static_cast(range)}); + } + else + { + ThrowError(args.Env(), "Bearing must be an array of [bearing, range] or null"); + return false; + } + } + else + { + ThrowError(args.Env(), "Bearing must be an array of [bearing, range] or null"); + return false; + } + } + } + + if (obj.Has("hints")) + { + Napi::Value hints = obj.Get("hints"); + if (hints.IsEmpty()) + return false; + + if (!hints.IsArray()) + { + ThrowError(args.Env(), "Hints must be an array of strings/null"); + return false; + } + + Napi::Array hints_array = hints.As(); + + if (hints_array.Length() != params->coordinates.size()) + { + ThrowError(args.Env(), "Hints array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < hints_array.Length(); ++i) + { + Napi::Value hint = hints_array.Get(i); + if (hint.IsEmpty()) + return false; + + if (hint.IsString()) + { + if (hint.ToString().Utf8Value().length() == 0) + { + ThrowError(args.Env(), "Hint cannot be an empty string"); + return false; + } + + params->hints.emplace_back( + osrm::engine::Hint::FromBase64(hint.ToString().Utf8Value())); + } + else if (hint.IsNull()) + { + params->hints.emplace_back(); + } + else + { + ThrowError(args.Env(), "Hint must be null or string"); + return false; + } + } + } + + if (obj.Has("radiuses")) + { + Napi::Value radiuses = obj.Get("radiuses"); + if (radiuses.IsEmpty()) + return false; + + if (!radiuses.IsArray()) + { + ThrowError(args.Env(), "Radiuses must be an array of non-negative doubles or null"); + return false; + } + + Napi::Array radiuses_array = radiuses.As(); + + if (radiuses_array.Length() != params->coordinates.size()) + { + ThrowError(args.Env(), "Radiuses array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < radiuses_array.Length(); ++i) + { + Napi::Value radius = radiuses_array.Get(i); + if (radius.IsEmpty()) + return false; + + if (radius.IsNull()) + { + params->radiuses.emplace_back(); + } + else if (radius.IsNumber() && radius.ToNumber().DoubleValue() >= 0) + { + params->radiuses.push_back(radius.ToNumber().DoubleValue()); + } + else + { + ThrowError(args.Env(), "Radius must be non-negative double or null"); + return false; + } + } + } + + if (obj.Has("generate_hints")) + { + Napi::Value generate_hints = obj.Get("generate_hints"); + if (generate_hints.IsEmpty()) + return false; + + if (!generate_hints.IsBoolean()) + { + ThrowError(args.Env(), "generate_hints must be of type Boolean"); + return false; + } + + params->generate_hints = generate_hints.ToBoolean().Value(); + } + + if (obj.Has("skip_waypoints")) + { + Napi::Value skip_waypoints = obj.Get("skip_waypoints"); + if (skip_waypoints.IsEmpty()) + return false; + + if (!skip_waypoints.IsBoolean()) + { + ThrowError(args.Env(), "skip_waypoints must be of type Boolean"); + return false; + } + + params->skip_waypoints = skip_waypoints.ToBoolean().Value(); + } + + if (obj.Has("exclude")) + { + Napi::Value exclude = obj.Get("exclude"); + if (exclude.IsEmpty()) + return false; + + if (!exclude.IsArray()) + { + ThrowError(args.Env(), "Exclude must be an array of strings or empty"); + return false; + } + + Napi::Array exclude_array = exclude.As(); + + for (uint32_t i = 0; i < exclude_array.Length(); ++i) + { + Napi::Value class_name = exclude_array.Get(i); + if (class_name.IsEmpty()) + return false; + + if (class_name.IsString()) + { + std::string class_name_str = class_name.ToString().Utf8Value(); + params->exclude.emplace_back(std::move(class_name_str)); + } + else + { + ThrowError(args.Env(), "Exclude must be an array of strings or empty"); + return false; + } + } + } + + if (obj.Has("format")) + { + Napi::Value format = obj.Get("format"); + if (format.IsEmpty()) + { + return false; + } + + if (!format.IsString()) + { + ThrowError(args.Env(), "format must be a string: \"json\" or \"flatbuffers\""); + return false; + } + + std::string format_str = format.ToString().Utf8Value(); + if (format_str == "json") + { + params->format = osrm::engine::api::BaseParameters::OutputFormatType::JSON; + } + else if (format_str == "flatbuffers") + { + params->format = osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS; + } + else + { + ThrowError(args.Env(), "format must be a string: \"json\" or \"flatbuffers\""); + return false; + } + } + + if (obj.Has("snapping")) + { + Napi::Value snapping = obj.Get("snapping"); + if (snapping.IsEmpty()) + return false; + + if (!snapping.IsString()) + { + ThrowError(args.Env(), "Snapping must be a string: [default, any]"); + return false; + } + + std::string snapping_str = snapping.ToString().Utf8Value(); + + if (snapping_str == "default") + { + params->snapping = osrm::RouteParameters::SnappingType::Default; + } + else if (snapping_str == "any") + { + params->snapping = osrm::RouteParameters::SnappingType::Any; + } + else + { + ThrowError(args.Env(), "'snapping' param must be one of [default, any]"); + return false; + } + } + + return true; +} + +template +inline bool parseCommonParameters(const Napi::Object &obj, ParamType ¶ms) +{ + if (obj.Has("steps")) + { + auto steps = obj.Get("steps"); + if (steps.IsEmpty()) + return false; + + if (steps.IsBoolean()) + { + params->steps = steps.ToBoolean().Value(); + } + else + { + ThrowError(obj.Env(), "'steps' param must be a boolean"); + return false; + } + } + + if (obj.Has("annotations")) + { + auto annotations = obj.Get("annotations"); + if (annotations.IsEmpty()) + return false; + + if (annotations.IsBoolean()) + { + params->annotations = annotations.ToBoolean().Value(); + params->annotations_type = params->annotations + ? osrm::RouteParameters::AnnotationsType::All + : osrm::RouteParameters::AnnotationsType::None; + } + else if (annotations.IsArray()) + { + Napi::Array annotations_array = annotations.As(); + for (std::size_t i = 0; i < annotations_array.Length(); i++) + { + std::string annotations_str = annotations_array.Get(i).ToString().Utf8Value(); + + if (annotations_str == "duration") + { + params->annotations_type = + params->annotations_type | osrm::RouteParameters::AnnotationsType::Duration; + } + else if (annotations_str == "nodes") + { + params->annotations_type = + params->annotations_type | osrm::RouteParameters::AnnotationsType::Nodes; + } + else if (annotations_str == "distance") + { + params->annotations_type = + params->annotations_type | osrm::RouteParameters::AnnotationsType::Distance; + } + else if (annotations_str == "weight") + { + params->annotations_type = + params->annotations_type | osrm::RouteParameters::AnnotationsType::Weight; + } + else if (annotations_str == "datasources") + { + params->annotations_type = params->annotations_type | + osrm::RouteParameters::AnnotationsType::Datasources; + } + else if (annotations_str == "speed") + { + params->annotations_type = + params->annotations_type | osrm::RouteParameters::AnnotationsType::Speed; + } + else + { + ThrowError(obj.Env(), "this 'annotations' param is not supported"); + return false; + } + + params->annotations = + params->annotations_type != osrm::RouteParameters::AnnotationsType::None; + } + } + else + { + ThrowError(obj.Env(), "this 'annotations' param is not supported"); + return false; + } + } + + if (obj.Has("geometries")) + { + Napi::Value geometries = obj.Get("geometries"); + if (geometries.IsEmpty()) + return false; + + if (!geometries.IsString()) + { + ThrowError(obj.Env(), "Geometries must be a string: [polyline, polyline6, geojson]"); + return false; + } + std::string geometries_str = geometries.ToString().Utf8Value(); + + if (geometries_str == "polyline") + { + params->geometries = osrm::RouteParameters::GeometriesType::Polyline; + } + else if (geometries_str == "polyline6") + { + params->geometries = osrm::RouteParameters::GeometriesType::Polyline6; + } + else if (geometries_str == "geojson") + { + params->geometries = osrm::RouteParameters::GeometriesType::GeoJSON; + } + else + { + ThrowError(obj.Env(), + "'geometries' param must be one of [polyline, polyline6, geojson]"); + return false; + } + } + + if (obj.Has("overview")) + { + Napi::Value overview = obj.Get("overview"); + if (overview.IsEmpty()) + return false; + + if (!overview.IsString()) + { + ThrowError(obj.Env(), "Overview must be a string: [simplified, full, false]"); + return false; + } + + std::string overview_str = overview.ToString().Utf8Value(); + + if (overview_str == "simplified") + { + params->overview = osrm::RouteParameters::OverviewType::Simplified; + } + else if (overview_str == "full") + { + params->overview = osrm::RouteParameters::OverviewType::Full; + } + else if (overview_str == "false") + { + params->overview = osrm::RouteParameters::OverviewType::False; + } + else + { + ThrowError(obj.Env(), "'overview' param must be one of [simplified, full, false]"); + return false; + } + } + + return true; +} + +inline PluginParameters argumentsToPluginParameters( + const Napi::CallbackInfo &args, + const boost::optional &output_format = {}) +{ + if (args.Length() < 3 || !args[1].IsObject()) + { + // output to buffer by default for Flatbuffers + return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS}; + } + Napi::Object obj = args[1].As(); + if (obj.Has("format")) + { + Napi::Value format = obj.Get("format"); + if (format.IsEmpty()) + { + return {}; + } + + if (!format.IsString()) + { + ThrowError(args.Env(), "format must be a string: \"object\" or \"buffer\""); + return {}; + } + + std::string format_str = format.ToString().Utf8Value(); + + if (format_str == "object") + { + if (output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS) + { + ThrowError(args.Env(), "Flatbuffers result can only output to buffer."); + return {true}; + } + return {false}; + } + else if (format_str == "buffer") + { + return {true}; + } + else if (format_str == "json_buffer") + { + if (output_format && + output_format != osrm::engine::api::BaseParameters::OutputFormatType::JSON) + { + ThrowError(args.Env(), + "Deprecated `json_buffer` can only be used with JSON format"); + } + return {true}; + } + else + { + ThrowError(args.Env(), "format must be a string: \"object\" or \"buffer\""); + return {}; + } + } + + // output to buffer by default for Flatbuffers + return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS}; +} + +inline route_parameters_ptr argumentsToRouteParameter(const Napi::CallbackInfo &args, + bool requires_multiple_coordinates) +{ + route_parameters_ptr params = std::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return route_parameters_ptr(); + + Napi::Object obj = args[0].As(); + + if (obj.Has("continue_straight")) + { + auto value = obj.Get("continue_straight"); + if (value.IsEmpty()) + return route_parameters_ptr(); + + if (!value.IsBoolean() && !value.IsNull()) + { + ThrowError(args.Env(), "'continue_straight' param must be boolean or null"); + return route_parameters_ptr(); + } + if (value.IsBoolean()) + { + params->continue_straight = value.ToBoolean().Value(); + } + } + + if (obj.Has("alternatives")) + { + auto value = obj.Get("alternatives"); + if (value.IsEmpty()) + return route_parameters_ptr(); + + if (value.IsBoolean()) + { + params->alternatives = value.ToBoolean().Value(); + params->number_of_alternatives = value.ToBoolean().Value() ? 1u : 0u; + } + else if (value.IsNumber()) + { + params->alternatives = value.ToBoolean().Value(); + params->number_of_alternatives = value.ToNumber().Int32Value(); + } + else + { + ThrowError(args.Env(), "'alternatives' param must be boolean or number"); + return route_parameters_ptr(); + } + } + + if (obj.Has("waypoints")) + { + Napi::Value waypoints = obj.Get("waypoints"); + if (waypoints.IsEmpty()) + return route_parameters_ptr(); + + // must be array + if (!waypoints.IsArray()) + { + ThrowError( + args.Env(), + "Waypoints must be an array of integers corresponding to the input coordinates."); + return route_parameters_ptr(); + } + + auto waypoints_array = waypoints.As(); + // must have at least two elements + if (waypoints_array.Length() < 2) + { + ThrowError(args.Env(), "At least two waypoints must be provided"); + return route_parameters_ptr(); + } + auto coords_size = params->coordinates.size(); + auto waypoints_array_size = waypoints_array.Length(); + + const auto first_index = + waypoints_array.Get(static_cast(0)).ToNumber().Uint32Value(); + const auto last_index = + waypoints_array.Get(waypoints_array_size - 1).ToNumber().Uint32Value(); + if (first_index != 0 || last_index != coords_size - 1) + { + ThrowError(args.Env(), + "First and last waypoints values must correspond to first and last " + "coordinate indices"); + return route_parameters_ptr(); + } + + for (uint32_t i = 0; i < waypoints_array_size; ++i) + { + Napi::Value waypoint_value = waypoints_array.Get(i); + // all elements must be numbers + if (!waypoint_value.IsNumber()) + { + ThrowError(args.Env(), "Waypoint values must be an array of integers"); + return route_parameters_ptr(); + } + // check that the waypoint index corresponds with an inpute coordinate + const auto index = waypoint_value.ToNumber().Uint32Value(); + if (index >= coords_size) + { + ThrowError(args.Env(), + "Waypoints must correspond with the index of an input coordinate"); + return route_parameters_ptr(); + } + params->waypoints.emplace_back(index); + } + + if (!params->waypoints.empty()) + { + for (std::size_t i = 0; i < params->waypoints.size() - 1; i++) + { + if (params->waypoints[i] >= params->waypoints[i + 1]) + { + ThrowError(args.Env(), "Waypoints must be supplied in increasing order"); + return route_parameters_ptr(); + } + } + } + } + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return route_parameters_ptr(); + } + + return params; +} + +inline tile_parameters_ptr argumentsToTileParameters(const Napi::CallbackInfo &args, + bool /*unused*/) +{ + tile_parameters_ptr params = std::make_unique(); + + if (args.Length() < 2) + { + ThrowTypeError(args.Env(), "Coordinate object and callback required"); + return tile_parameters_ptr(); + } + + if (!args[0].IsArray()) + { + ThrowTypeError(args.Env(), "Parameter must be an array [x, y, z]"); + return tile_parameters_ptr(); + } + + Napi::Array array = args[0].As(); + + if (array.Length() != 3) + { + ThrowTypeError(args.Env(), "Parameter must be an array [x, y, z]"); + return tile_parameters_ptr(); + } + + Napi::Value x = array.Get(static_cast(0)); + Napi::Value y = array.Get(static_cast(1)); + Napi::Value z = array.Get(static_cast(2)); + if (x.IsEmpty() || y.IsEmpty() || z.IsEmpty()) + return tile_parameters_ptr(); + + if (!IsUnsignedInteger(x) && !x.IsUndefined()) + { + ThrowError(args.Env(), "Tile x coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + if (!IsUnsignedInteger(y) && !y.IsUndefined()) + { + ThrowError(args.Env(), "Tile y coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + if (!IsUnsignedInteger(z) && !z.IsUndefined()) + { + ThrowError(args.Env(), "Tile z coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + + params->x = x.ToNumber().Uint32Value(); + params->y = y.ToNumber().Uint32Value(); + params->z = z.ToNumber().Uint32Value(); + + if (!params->IsValid()) + { + ThrowError(args.Env(), "Invalid tile coordinates"); + return tile_parameters_ptr(); + } + + return params; +} + +inline nearest_parameters_ptr argumentsToNearestParameter(const Napi::CallbackInfo &args, + bool requires_multiple_coordinates) +{ + nearest_parameters_ptr params = std::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return nearest_parameters_ptr(); + + Napi::Object obj = args[0].As(); + if (obj.IsEmpty()) + return nearest_parameters_ptr(); + + if (obj.Has("number")) + { + Napi::Value number = obj.Get("number"); + + if (!IsUnsignedInteger(number)) + { + ThrowError(args.Env(), "Number must be an integer greater than or equal to 1"); + return nearest_parameters_ptr(); + } + else + { + unsigned number_value = number.ToNumber().Uint32Value(); + + if (number_value < 1) + { + ThrowError(args.Env(), "Number must be an integer greater than or equal to 1"); + return nearest_parameters_ptr(); + } + + params->number_of_results = number_value; + } + } + + return params; +} + +inline table_parameters_ptr argumentsToTableParameter(const Napi::CallbackInfo &args, + bool requires_multiple_coordinates) +{ + table_parameters_ptr params = std::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return table_parameters_ptr(); + + Napi::Object obj = args[0].As(); + if (obj.IsEmpty()) + return table_parameters_ptr(); + + if (obj.Has("sources")) + { + Napi::Value sources = obj.Get("sources"); + if (sources.IsEmpty()) + return table_parameters_ptr(); + + if (!sources.IsArray()) + { + ThrowError(args.Env(), "Sources must be an array of indices (or undefined)"); + return table_parameters_ptr(); + } + + Napi::Array sources_array = sources.As(); + for (uint32_t i = 0; i < sources_array.Length(); ++i) + { + Napi::Value source = sources_array.Get(i); + if (source.IsEmpty()) + return table_parameters_ptr(); + + if (IsUnsignedInteger(source)) + { + size_t source_value = source.ToNumber().Uint32Value(); + if (source_value >= params->coordinates.size()) + { + ThrowError(args.Env(), + "Source indices must be less than the number of coordinates"); + return table_parameters_ptr(); + } + + params->sources.push_back(source.ToNumber().Uint32Value()); + } + else + { + ThrowError(args.Env(), "Source must be an integer"); + return table_parameters_ptr(); + } + } + } + + if (obj.Has("destinations")) + { + Napi::Value destinations = obj.Get("destinations"); + if (destinations.IsEmpty()) + return table_parameters_ptr(); + + if (!destinations.IsArray()) + { + ThrowError(args.Env(), "Destinations must be an array of indices (or undefined)"); + return table_parameters_ptr(); + } + + Napi::Array destinations_array = destinations.As(); + for (uint32_t i = 0; i < destinations_array.Length(); ++i) + { + Napi::Value destination = destinations_array.Get(i); + if (destination.IsEmpty()) + return table_parameters_ptr(); + + if (IsUnsignedInteger(destination)) + { + size_t destination_value = destination.ToNumber().Uint32Value(); + if (destination_value >= params->coordinates.size()) + { + ThrowError(args.Env(), + "Destination indices must be less than the number " + "of coordinates"); + return table_parameters_ptr(); + } + + params->destinations.push_back(destination_value); + } + else + { + ThrowError(args.Env(), "Destination must be an integer"); + return table_parameters_ptr(); + } + } + } + + if (obj.Has("annotations")) + { + Napi::Value annotations = obj.Get("annotations"); + if (annotations.IsEmpty()) + return table_parameters_ptr(); + + if (!annotations.IsArray()) + { + ThrowError(args.Env(), + "Annotations must an array containing 'duration' or 'distance', or both"); + return table_parameters_ptr(); + } + + params->annotations = osrm::TableParameters::AnnotationsType::None; + + Napi::Array annotations_array = annotations.As(); + for (std::size_t i = 0; i < annotations_array.Length(); ++i) + { + std::string annotations_str = annotations_array.Get(i).ToString().Utf8Value(); + + if (annotations_str == "duration") + { + params->annotations = + params->annotations | osrm::TableParameters::AnnotationsType::Duration; + } + else if (annotations_str == "distance") + { + params->annotations = + params->annotations | osrm::TableParameters::AnnotationsType::Distance; + } + else + { + ThrowError(args.Env(), "this 'annotations' param is not supported"); + return table_parameters_ptr(); + } + } + } + + if (obj.Has("fallback_speed")) + { + auto fallback_speed = obj.Get("fallback_speed"); + + if (!fallback_speed.IsNumber()) + { + ThrowError(args.Env(), "fallback_speed must be a number"); + return table_parameters_ptr(); + } + else if (fallback_speed.ToNumber().DoubleValue() <= 0) + { + ThrowError(args.Env(), "fallback_speed must be > 0"); + return table_parameters_ptr(); + } + + params->fallback_speed = fallback_speed.ToNumber().DoubleValue(); + } + + if (obj.Has("fallback_coordinate")) + { + auto fallback_coordinate = obj.Get("fallback_coordinate"); + + if (!fallback_coordinate.IsString()) + { + ThrowError(args.Env(), "fallback_coordinate must be a string: [input, snapped]"); + return table_parameters_ptr(); + } + + std::string fallback_coordinate_str = fallback_coordinate.ToString().Utf8Value(); + + if (fallback_coordinate_str == "snapped") + { + params->fallback_coordinate_type = + osrm::TableParameters::FallbackCoordinateType::Snapped; + } + else if (fallback_coordinate_str == "input") + { + params->fallback_coordinate_type = osrm::TableParameters::FallbackCoordinateType::Input; + } + else + { + ThrowError(args.Env(), "'fallback_coordinate' param must be one of [input, snapped]"); + return table_parameters_ptr(); + } + } + + if (obj.Has("scale_factor")) + { + auto scale_factor = obj.Get("scale_factor"); + + if (!scale_factor.IsNumber()) + { + ThrowError(args.Env(), "scale_factor must be a number"); + return table_parameters_ptr(); + } + else if (scale_factor.ToNumber().DoubleValue() <= 0) + { + ThrowError(args.Env(), "scale_factor must be > 0"); + return table_parameters_ptr(); + } + + params->scale_factor = scale_factor.ToNumber().DoubleValue(); + } + + return params; +} + +inline trip_parameters_ptr argumentsToTripParameter(const Napi::CallbackInfo &args, + bool requires_multiple_coordinates) +{ + trip_parameters_ptr params = std::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return trip_parameters_ptr(); + + Napi::Object obj = args[0].As(); + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return trip_parameters_ptr(); + } + + if (obj.Has("roundtrip")) + { + auto roundtrip = obj.Get("roundtrip"); + if (roundtrip.IsEmpty()) + return trip_parameters_ptr(); + + if (roundtrip.IsBoolean()) + { + params->roundtrip = roundtrip.ToBoolean().Value(); + } + else + { + ThrowError(args.Env(), "'roundtrip' param must be a boolean"); + return trip_parameters_ptr(); + } + } + + if (obj.Has("source")) + { + Napi::Value source = obj.Get("source"); + if (source.IsEmpty()) + return trip_parameters_ptr(); + + if (!source.IsString()) + { + ThrowError(args.Env(), "Source must be a string: [any, first]"); + return trip_parameters_ptr(); + } + + std::string source_str = source.ToString().Utf8Value(); + + if (source_str == "first") + { + params->source = osrm::TripParameters::SourceType::First; + } + else if (source_str == "any") + { + params->source = osrm::TripParameters::SourceType::Any; + } + else + { + ThrowError(args.Env(), "'source' param must be one of [any, first]"); + return trip_parameters_ptr(); + } + } + + if (obj.Has("destination")) + { + Napi::Value destination = obj.Get("destination"); + if (destination.IsEmpty()) + return trip_parameters_ptr(); + + if (!destination.IsString()) + { + ThrowError(args.Env(), "Destination must be a string: [any, last]"); + return trip_parameters_ptr(); + } + + std::string destination_str = destination.ToString().Utf8Value(); + + if (destination_str == "last") + { + params->destination = osrm::TripParameters::DestinationType::Last; + } + else if (destination_str == "any") + { + params->destination = osrm::TripParameters::DestinationType::Any; + } + else + { + ThrowError(args.Env(), "'destination' param must be one of [any, last]"); + return trip_parameters_ptr(); + } + } + + return params; +} + +inline match_parameters_ptr argumentsToMatchParameter(const Napi::CallbackInfo &args, + bool requires_multiple_coordinates) +{ + match_parameters_ptr params = std::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return match_parameters_ptr(); + + Napi::Object obj = args[0].As(); + + if (obj.Has("timestamps")) + { + Napi::Value timestamps = obj.Get("timestamps"); + if (timestamps.IsEmpty()) + return match_parameters_ptr(); + + if (!timestamps.IsArray()) + { + ThrowError(args.Env(), "Timestamps must be an array of integers (or undefined)"); + return match_parameters_ptr(); + } + + Napi::Array timestamps_array = timestamps.As(); + + if (params->coordinates.size() != timestamps_array.Length()) + { + ThrowError(args.Env(), + "Timestamp array must have the same size as the coordinates " + "array"); + return match_parameters_ptr(); + } + + for (uint32_t i = 0; i < timestamps_array.Length(); ++i) + { + Napi::Value timestamp = timestamps_array.Get(i); + if (timestamp.IsEmpty()) + return match_parameters_ptr(); + + if (!timestamp.IsNumber()) + { + ThrowError(args.Env(), "Timestamps array items must be numbers"); + return match_parameters_ptr(); + } + params->timestamps.emplace_back(timestamp.ToNumber().Int64Value()); + } + } + + if (obj.Has("gaps")) + { + Napi::Value gaps = obj.Get("gaps"); + if (gaps.IsEmpty()) + return match_parameters_ptr(); + + if (!gaps.IsString()) + { + ThrowError(args.Env(), "Gaps must be a string: [split, ignore]"); + return match_parameters_ptr(); + } + + std::string gaps_str = gaps.ToString().Utf8Value(); + + if (gaps_str == "split") + { + params->gaps = osrm::MatchParameters::GapsType::Split; + } + else if (gaps_str == "ignore") + { + params->gaps = osrm::MatchParameters::GapsType::Ignore; + } + else + { + ThrowError(args.Env(), "'gaps' param must be one of [split, ignore]"); + return match_parameters_ptr(); + } + } + + if (obj.Has("tidy")) + { + Napi::Value tidy = obj.Get("tidy"); + if (tidy.IsEmpty()) + return match_parameters_ptr(); + + if (!tidy.IsBoolean()) + { + ThrowError(args.Env(), "tidy must be of type Boolean"); + return match_parameters_ptr(); + } + + params->tidy = tidy.ToBoolean().Value(); + } + + if (obj.Has("waypoints")) + { + Napi::Value waypoints = obj.Get("waypoints"); + if (waypoints.IsEmpty()) + return match_parameters_ptr(); + + // must be array + if (!waypoints.IsArray()) + { + ThrowError( + args.Env(), + "Waypoints must be an array of integers corresponding to the input coordinates."); + return match_parameters_ptr(); + } + + auto waypoints_array = waypoints.As(); + // must have at least two elements + if (waypoints_array.Length() < 2) + { + ThrowError(args.Env(), "At least two waypoints must be provided"); + return match_parameters_ptr(); + } + auto coords_size = params->coordinates.size(); + auto waypoints_array_size = waypoints_array.Length(); + + const auto first_index = + waypoints_array.Get(static_cast(0)).ToNumber().Uint32Value(); + const auto last_index = + waypoints_array.Get(waypoints_array_size - 1).ToNumber().Uint32Value(); + if (first_index != 0 || last_index != coords_size - 1) + { + ThrowError(args.Env(), + "First and last waypoints values must correspond to first and last " + "coordinate indices"); + return match_parameters_ptr(); + } + + for (uint32_t i = 0; i < waypoints_array_size; ++i) + { + Napi::Value waypoint_value = waypoints_array.Get(i); + // all elements must be numbers + if (!waypoint_value.IsNumber()) + { + ThrowError(args.Env(), "Waypoint values must be an array of integers"); + return match_parameters_ptr(); + } + // check that the waypoint index corresponds with an inpute coordinate + const auto index = waypoint_value.ToNumber().Uint32Value(); + if (index >= coords_size) + { + ThrowError(args.Env(), + "Waypoints must correspond with the index of an input coordinate"); + return match_parameters_ptr(); + } + params->waypoints.emplace_back(index); + } + } + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return match_parameters_ptr(); + } + + return params; +} + +} // namespace node_osrm + +#endif