diff --git a/.gemfiles/google-protobuf.gemfile b/.gemfiles/google-protobuf.gemfile new file mode 100644 index 0000000..9845839 --- /dev/null +++ b/.gemfiles/google-protobuf.gemfile @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'google-protobuf', '~> 3.21', '>= 3.21.9' \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml index 46b0c20..4d0fad0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,6 +9,11 @@ AllCops: DisplayStyleGuide: true TargetRubyVersion: 2.7 NewCops: enable + Exclude: + - spec/*.rb + - lib/travel_time/proto/v2/*.rb + - vendor/**/* + - gemfiles/vendor/bundle/**/* # Disabling until TravelTime Rubygems account is MFA-protected. Gemspec/RequireMFA: @@ -24,8 +29,7 @@ RSpec/NestedGroups: RSpec/MultipleMemoizedHelpers: Max: 10 -RSpec/Rails/HaveHttpStatus: - Enabled: false - Metrics/ParameterLists: + Enabled: false +RSpec/Rails/HaveHttpStatus: Enabled: false \ No newline at end of file diff --git a/README.md b/README.md index 2dd27b5..1648839 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,75 @@ response = client.time_filter_fast( puts response.body ``` +### [Time Filter (Fast) Proto](https://docs.traveltime.com/api/reference/travel-time-distance-matrix-proto) +A fast version of time filter communicating using [protocol buffers](https://github.com/protocolbuffers/protobuf). + +The request parameters are much more limited and only travel time is returned. In addition, the results are only approximately correct (95% of the results are guaranteed to be within 5% of the routes returned by regular time filter). + +This inflexibility comes with a benefit of faster response times (Over 5x faster compared to regular time filter) and larger limits on the amount of destination points. + +Body attributes: +* origin: Origin point. +* destinations: Destination points. Cannot be more than 200,000. +* transport: Transportation type. +* travelTime: Time limit; +* country: Return the results that are within the specified country + +```ruby +origin = { + lat: 51.508930, + lng: -0.131387, +} + +destinations = [{ + lat: 51.508824, + lng: -0.167093, +}] + +response = client.time_filter_fast_proto( + country: 'UK', + origin: origin, + destinations: destinations, + transport: 'driving+ferry', + traveltime: 7200 +) +puts(response.body) +``` + +### Time Filter (Fast) Proto Distance +A version of `Time Filter (Fast) Proto` endpoint that also returns distance information. Request parameters are even more limited than `Time Filter (Fast) Proto`. + +This endpoint is not enabled by default, please [contact us](https://traveltime.com/contact-us) if you wish to obtain access. + +Body attributes: +* origin: Origin point. +* destinations: Destination points. Cannot be more than 200,000. +* transport: Transportation type. +* travelTime: Time limit; +* country: Return the results that are within the specified country + +```ruby +origin = { + lat: 51.508930, + lng: -0.131387, +} + +destinations = [{ + lat: 51.508824, + lng: -0.167093, +}] + +response = client.time_filter_fast_proto_distance( + country: 'UK', + origin: origin, + destinations: destinations, + transport: 'driving+ferry', + traveltime: 7200 +) +puts(response.body) +``` + + ### [Time Filter (Postcode Districts)](https://traveltime.com/docs/api/reference/postcode-district-filter) Find districts that have a certain coverage from origin (or to destination) and get statistics about postcodes within such districts. Currently only supports United Kingdom. diff --git a/lib/travel_time.rb b/lib/travel_time.rb index 050261e..77d7e95 100644 --- a/lib/travel_time.rb +++ b/lib/travel_time.rb @@ -5,6 +5,7 @@ require 'travel_time/error' require 'travel_time/response' require 'travel_time/version' +require 'travel_time/proto/utils' # Main TravelTime module module TravelTime diff --git a/lib/travel_time/client.rb b/lib/travel_time/client.rb index c00da17..f80bac8 100644 --- a/lib/travel_time/client.rb +++ b/lib/travel_time/client.rb @@ -2,13 +2,14 @@ require 'faraday' require 'travel_time/middleware/authentication' +require 'travel_time/middleware/proto' module TravelTime # The Client class provides the main interface to interact with the TravelTime API class Client # rubocop:disable Metrics/ClassLength API_BASE_URL = 'https://api.traveltimeapp.com/v4/' - attr_reader :connection + attr_reader :connection, :proto_connection def initialize @connection = Faraday.new(API_BASE_URL) do |f| @@ -19,12 +20,27 @@ def initialize f.use TravelTime::Middleware::Authentication f.adapter TravelTime.config.http_adapter || Faraday.default_adapter end + + init_proto_connection + end + + def init_proto_connection + @proto_connection = Faraday.new do |f| + f.use TravelTime::Middleware::ProtoMiddleware + f.response :raise_error if TravelTime.config.raise_on_failure + f.response :logger if TravelTime.config.enable_logging + f.adapter TravelTime.config.http_adapter || Faraday.default_adapter + end end def unwrap(response) Response.from_object(response) end + def unwrap_proto(response) + Response.from_object_proto(response) + end + def perform_request unwrap(yield) rescue Faraday::Error => e @@ -35,6 +51,12 @@ def perform_request raise TravelTime::Error.new(exception: e) end + def perform_request_proto + unwrap_proto(yield) + rescue StandardError => e + raise TravelTime::Error.new(exception: e) + end + def map_info perform_request { connection.get('map-info') } end @@ -94,6 +116,24 @@ def time_filter_fast(locations:, arrival_searches:) perform_request { connection.post('time-filter/fast', payload) } end + def time_filter_fast_proto(country:, origin:, destinations:, transport:, traveltime:) + message = ProtoUtils.make_proto_message(origin, destinations, transport, traveltime) + payload = ProtoUtils.encode_proto_message(message) + perform_request_proto do + proto_connection.post("http://proto.api.traveltimeapp.com/api/v2/#{country}/time-filter/fast/#{transport}", + payload) + end + end + + def time_filter_fast_proto_distance(country:, origin:, destinations:, transport:, traveltime:) + message = ProtoUtils.make_proto_message(origin, destinations, transport, traveltime, properties: [1]) + payload = ProtoUtils.encode_proto_message(message) + perform_request_proto do + proto_connection.post("https://proto-with-distance.api.traveltimeapp.com/api/v2/#{country}/time-filter/fast/#{transport}", + payload) + end + end + def time_filter_postcodes(departure_searches: nil, arrival_searches: nil) payload = { departure_searches: departure_searches, diff --git a/lib/travel_time/middleware/proto.rb b/lib/travel_time/middleware/proto.rb new file mode 100644 index 0000000..bb016ad --- /dev/null +++ b/lib/travel_time/middleware/proto.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'faraday' +require 'base64' + +module TravelTime + module Middleware + # The Proto middleware is responsible for setting the basic auth headers for proto requests + # on each request. These are automatically taken from the `TravelTime.config`. + class ProtoMiddleware < Faraday::Middleware + def on_request(env) + env.request_headers['Authorization'] = + "Basic #{Base64.encode64("#{TravelTime.config.application_id}:#{TravelTime.config.api_key}")}" + env.request_headers['Content-Type'] = 'application/octet-stream' + env.request_headers['Accept'] = 'application/octet-stream' + end + end + end +end diff --git a/lib/travel_time/proto/source/RequestsCommon.proto b/lib/travel_time/proto/source/RequestsCommon.proto new file mode 100644 index 0000000..6621d1c --- /dev/null +++ b/lib/travel_time/proto/source/RequestsCommon.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package com.igeolise.traveltime.rabbitmq.requests; + +message Coords { + float lat = 1; + float lng = 2; +} + +message Transportation { + TransportationType type = 1; +} + +enum TransportationType { + // Considers all paths found by the following steps: + // * up to 30 minutes of walking (always included even if no stops found) + // * all connections in the 30 minute walking range from public transport + // stops to other public transport stops in travel_time_limit, AND + // * up to 30 minutes of walking from public transport stops that were visited + // by public transport (IOW a path + // [origin]--walking->[stop]--walking-->[destination] is not possible but + // [origin]--walking->[stop]--public_transport-->[stop]--walking--> is. + PUBLIC_TRANSPORT = 0; + // Considers all paths found traveling by car from origin(s) to + // destination(s) within the travel_time_limit + DRIVING = 1; + // Considers all paths found by the following steps: + // * up to 30 minutes of driving (always included even no stops found) + // * all connections in the 30 minute driving range from public transport stops + // to other public transport stops in travel_time_limit, AND + // * up to 30 minutes of walking from public transport stops that were visited + // by public transport (IOW a path + // [origin]--driving->[stop]--walking-->[destination] is not possible but + // [origin]--driving->[stop]--public_transport-->[stop]--walking--> is. + // AND/OR + // * up to 30 minutes of walking + // + DRIVING_AND_PUBLIC_TRANSPORT = 2; + // Considers all paths found travelling by car from origin(s) to + // destination(s) including all paths that are traversable by ferries that + // take cars within the travel_time_limit. + DRIVING_AND_FERRY = 3; + // Considers all paths found travelling by foot from origin(s) to + // destination(s) within the travel_time_limit + WALKING = 4; + // Considers all paths found travelling by foot from origin(s) to + // destination(s) including all paths that are traversable by ferries that + // take passengers within the travel_time_limit + WALKING_AND_FERRY = 7; + // Considers all paths found travelling by bike from origin(s) to + // destination(s) within the travel_time_limit + CYCLING = 5; + // Considers all paths found travelling by bike from origin(s) to + // destination(s) including all paths that are traversable by ferries that + // take bikes within the travel_time_limit + CYCLING_AND_FERRY = 6; +} + +enum TimePeriod { + WEEKDAY_MORNING = 0; +} diff --git a/lib/travel_time/proto/source/TimeFilterFastRequest.proto b/lib/travel_time/proto/source/TimeFilterFastRequest.proto new file mode 100644 index 0000000..05c885c --- /dev/null +++ b/lib/travel_time/proto/source/TimeFilterFastRequest.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package com.igeolise.traveltime.rabbitmq.requests; + +import "RequestsCommon.proto"; + +message TimeFilterFastRequest { + enum Property { + FARES = 0; + DISTANCES = 1; + } + message OneToMany { + Coords departureLocation = 1; + /* + * We encode arrival locations as deltas (relative to the source) using a fixedpoint encoding i.e + * deltaLat = round((lat - sourceLat) * 10^5).toInt + * deltaLon = round((lon - sourceLon) * 10^5).toInt + * + * The deltas should be interleaved in the `locationDeltas` field i.e + * + * locationDeltas[0] should be the first lat + * locationDeltas[1] should be the first lon + * locationDeltas[2] should be the second lat + * ... + * etc + */ + repeated sint32 locationDeltas = 2; + Transportation transportation = 3; + TimePeriod arrivalTimePeriod = 4; + sint32 travelTime = 5; + repeated Property properties = 6; + } + + OneToMany oneToManyRequest = 1; +} \ No newline at end of file diff --git a/lib/travel_time/proto/source/TimeFilterFastResponse.proto b/lib/travel_time/proto/source/TimeFilterFastResponse.proto new file mode 100644 index 0000000..c2b86bb --- /dev/null +++ b/lib/travel_time/proto/source/TimeFilterFastResponse.proto @@ -0,0 +1,84 @@ +syntax = "proto3"; + +package com.igeolise.traveltime.rabbitmq.responses; + +message TimeFilterFastResponse { + message Properties { + repeated sint32 travelTimes = 1; + repeated int32 monthlyFares = 2; + repeated int32 distances = 3; + } + + message Error { + ErrorType type = 1; + } + + enum ErrorType { + /* + * Catch all unknown error type + */ + UNKNOWN = 0; + /* + * oneToManyRequest to many field must not be null + */ + ONE_TO_MANY_MUST_NOT_BE_NULL = 1; + /* + * Source (either departure or arrival location) must not be null + */ + SOURCE_MUST_NOT_BE_NULL = 2; + /* + * Transportation mode must not be null. + */ + TRANSPORTATION_MUST_NOT_BE_NULL = 3; + /* + * Source (either departure or arrival location) must not be null + */ + SOURCE_NOT_IN_GEOMETRY = 4; + + /* + * Transportation mode unrecognized. + */ + UNRECOGNIZED_TRANSPORTATION_MODE = 5; + + /* + * The travel time limit is too low to process this request. + */ + TRAVEL_TIME_LIMIT_TOO_LOW = 6; + + /* + * The travel time limit is too high to process this request. + */ + TRAVEL_TIME_LIMIT_TOO_HIGH = 7; + + /* + * User id not set. + */ + AUTH_ERROR_NO_USER_ID = 8; + + /* + * Message sent to wrong queue - transportation mode cannot be handled. + */ + SERVICE_MISMATCH_WRONG_TRANSPORTATION_MODE = 9; + + /* + * Source is in a area that doesn't have any points that can be out of + * search e.g a lake, mountains or other desolate areas. + */ + SOURCE_OUT_OF_REACH = 10; + + /* + * The interleaved deltas array should have (lat/lon) deltas and have an + * even number of elements + */ + INTERLEAVED_DELTAS_INVALID_COORDINATE_PAIRS = 11; + + /* + * Public transport requests do not support returning distances for + * returned points. + */ + DISTANCE_PROPERTY_NOT_SUPPORTED = 12; + } + + Error error = 1; + Properties properties = 2; +} diff --git a/lib/travel_time/proto/utils.rb b/lib/travel_time/proto/utils.rb new file mode 100644 index 0000000..411d7e3 --- /dev/null +++ b/lib/travel_time/proto/utils.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'travel_time/proto/v2/RequestsCommon_pb' +require 'travel_time/proto/v2/TimeFilterFastRequest_pb' +require 'travel_time/proto/v2/TimeFilterFastResponse_pb' + +module TravelTime + # Utilities for encoding/decoding protobuf requests + class ProtoUtils + def self.encode_fixed_point(source, target) + ((target - source) * 10.pow(5)).round + end + + def self.build_deltas(departure, destinations) + deltas = destinations.map do |destination| + [encode_fixed_point(departure[:lat], destination[:lat]), + encode_fixed_point(departure[:lng], destination[:lng])] + end + deltas.flatten + end + + def self.get_proto_transport_code(transport) + proto_transport_map = { + pt: 0, + 'driving+ferry': 3, + 'cycling+ferry': 6, + 'walking+ferry': 7 + } + proto_transport_map[transport.to_sym] + end + + def self.make_one_to_many(origin, destinations, transport, traveltime, properties) + Com::Igeolise::Traveltime::Rabbitmq::Requests::TimeFilterFastRequest::OneToMany.new( + departureLocation: origin, + locationDeltas: build_deltas(origin, destinations), + transportation: Com::Igeolise::Traveltime::Rabbitmq::Requests::Transportation.new( + { type: get_proto_transport_code(transport) } + ), + arrivalTimePeriod: 0, + travelTime: traveltime, + properties: properties + ) + end + + def self.make_proto_message(origin, destinations, transport, traveltime, properties: nil) + Com::Igeolise::Traveltime::Rabbitmq::Requests::TimeFilterFastRequest.new( + oneToManyRequest: make_one_to_many(origin, destinations, transport, traveltime, properties) + ) + end + + def self.encode_proto_message(message) + Com::Igeolise::Traveltime::Rabbitmq::Requests::TimeFilterFastRequest.encode(message) + end + + def self.decode_proto_response(response) + Com::Igeolise::Traveltime::Rabbitmq::Responses::TimeFilterFastResponse.decode(response).to_h + end + end +end diff --git a/lib/travel_time/proto/v2/RequestsCommon_pb.rb b/lib/travel_time/proto/v2/RequestsCommon_pb.rb new file mode 100644 index 0000000..751caa7 --- /dev/null +++ b/lib/travel_time/proto/v2/RequestsCommon_pb.rb @@ -0,0 +1,44 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: RequestsCommon.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("RequestsCommon.proto", :syntax => :proto3) do + add_message "com.igeolise.traveltime.rabbitmq.requests.Coords" do + optional :lat, :float, 1 + optional :lng, :float, 2 + end + add_message "com.igeolise.traveltime.rabbitmq.requests.Transportation" do + optional :type, :enum, 1, "com.igeolise.traveltime.rabbitmq.requests.TransportationType" + end + add_enum "com.igeolise.traveltime.rabbitmq.requests.TransportationType" do + value :PUBLIC_TRANSPORT, 0 + value :DRIVING, 1 + value :DRIVING_AND_PUBLIC_TRANSPORT, 2 + value :DRIVING_AND_FERRY, 3 + value :WALKING, 4 + value :WALKING_AND_FERRY, 7 + value :CYCLING, 5 + value :CYCLING_AND_FERRY, 6 + end + add_enum "com.igeolise.traveltime.rabbitmq.requests.TimePeriod" do + value :WEEKDAY_MORNING, 0 + end + end +end + +module Com + module Igeolise + module Traveltime + module Rabbitmq + module Requests + Coords = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.Coords").msgclass + Transportation = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.Transportation").msgclass + TransportationType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.TransportationType").enummodule + TimePeriod = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.TimePeriod").enummodule + end + end + end + end +end diff --git a/lib/travel_time/proto/v2/TimeFilterFastRequest_pb.rb b/lib/travel_time/proto/v2/TimeFilterFastRequest_pb.rb new file mode 100644 index 0000000..5509a75 --- /dev/null +++ b/lib/travel_time/proto/v2/TimeFilterFastRequest_pb.rb @@ -0,0 +1,40 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: TimeFilterFastRequest.proto + +require 'google/protobuf' + +require_relative 'RequestsCommon_pb' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("TimeFilterFastRequest.proto", :syntax => :proto3) do + add_message "com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest" do + optional :oneToManyRequest, :message, 1, "com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.OneToMany" + end + add_message "com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.OneToMany" do + optional :departureLocation, :message, 1, "com.igeolise.traveltime.rabbitmq.requests.Coords" + repeated :locationDeltas, :sint32, 2 + optional :transportation, :message, 3, "com.igeolise.traveltime.rabbitmq.requests.Transportation" + optional :arrivalTimePeriod, :enum, 4, "com.igeolise.traveltime.rabbitmq.requests.TimePeriod" + optional :travelTime, :sint32, 5 + repeated :properties, :enum, 6, "com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.Property" + end + add_enum "com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.Property" do + value :FARES, 0 + value :DISTANCES, 1 + end + end +end + +module Com + module Igeolise + module Traveltime + module Rabbitmq + module Requests + TimeFilterFastRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest").msgclass + TimeFilterFastRequest::OneToMany = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.OneToMany").msgclass + TimeFilterFastRequest::Property = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.requests.TimeFilterFastRequest.Property").enummodule + end + end + end + end +end diff --git a/lib/travel_time/proto/v2/TimeFilterFastResponse_pb.rb b/lib/travel_time/proto/v2/TimeFilterFastResponse_pb.rb new file mode 100644 index 0000000..1be6de5 --- /dev/null +++ b/lib/travel_time/proto/v2/TimeFilterFastResponse_pb.rb @@ -0,0 +1,51 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: TimeFilterFastResponse.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("TimeFilterFastResponse.proto", :syntax => :proto3) do + add_message "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse" do + optional :error, :message, 1, "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Error" + optional :properties, :message, 2, "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Properties" + end + add_message "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Properties" do + repeated :travelTimes, :sint32, 1 + repeated :monthlyFares, :int32, 2 + repeated :distances, :int32, 3 + end + add_message "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Error" do + optional :type, :enum, 1, "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.ErrorType" + end + add_enum "com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.ErrorType" do + value :UNKNOWN, 0 + value :ONE_TO_MANY_MUST_NOT_BE_NULL, 1 + value :SOURCE_MUST_NOT_BE_NULL, 2 + value :TRANSPORTATION_MUST_NOT_BE_NULL, 3 + value :SOURCE_NOT_IN_GEOMETRY, 4 + value :UNRECOGNIZED_TRANSPORTATION_MODE, 5 + value :TRAVEL_TIME_LIMIT_TOO_LOW, 6 + value :TRAVEL_TIME_LIMIT_TOO_HIGH, 7 + value :AUTH_ERROR_NO_USER_ID, 8 + value :SERVICE_MISMATCH_WRONG_TRANSPORTATION_MODE, 9 + value :SOURCE_OUT_OF_REACH, 10 + value :INTERLEAVED_DELTAS_INVALID_COORDINATE_PAIRS, 11 + value :DISTANCE_PROPERTY_NOT_SUPPORTED, 12 + end + end +end + +module Com + module Igeolise + module Traveltime + module Rabbitmq + module Responses + TimeFilterFastResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse").msgclass + TimeFilterFastResponse::Properties = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Properties").msgclass + TimeFilterFastResponse::Error = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.Error").msgclass + TimeFilterFastResponse::ErrorType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("com.igeolise.traveltime.rabbitmq.responses.TimeFilterFastResponse.ErrorType").enummodule + end + end + end + end +end diff --git a/lib/travel_time/response.rb b/lib/travel_time/response.rb index f751669..3139c96 100644 --- a/lib/travel_time/response.rb +++ b/lib/travel_time/response.rb @@ -15,6 +15,14 @@ def self.from_object(response) ) end + def self.from_object_proto(response) + new( + status: response.status, + headers: response.headers, + body: ProtoUtils.decode_proto_response(response.body) + ) + end + def self.from_hash(response) new( status: response[:status], diff --git a/lib/travel_time/version.rb b/lib/travel_time/version.rb index cc481ce..ba9cba8 100644 --- a/lib/travel_time/version.rb +++ b/lib/travel_time/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module TravelTime - VERSION = '0.3.3' + VERSION = '0.4.0' end diff --git a/spec/travel_time/client_spec.rb b/spec/travel_time/client_spec.rb index 5c806fe..404ae97 100644 --- a/spec/travel_time/client_spec.rb +++ b/spec/travel_time/client_spec.rb @@ -128,6 +128,34 @@ it_behaves_like 'an endpoint method' end + describe '#time_filter_fast_proto' do + country = 'uk' + transport = 'pt' + subject(:response) do + client.time_filter_fast_proto(country: country, origin: {}, destinations: {}, transport: transport, + traveltime: 0) + end + + let(:url) { "http://proto.api.traveltimeapp.com/api/v2/#{country}/time-filter/fast/#{transport}" } + let(:stub) { stub_request(:post, url) } + + it_behaves_like 'an endpoint method' + end + + describe '#time_filter_fast_proto_distance' do + country = 'uk' + transport = 'driving+ferry' + subject(:response) do + client.time_filter_fast_proto_distance(country: country, origin: {}, destinations: {}, transport: transport, + traveltime: 0) + end + + let(:url) { "https://proto-with-distance.api.traveltimeapp.com/api/v2/#{country}/time-filter/fast/#{transport}" } + let(:stub) { stub_request(:post, url) } + + it_behaves_like 'an endpoint method' + end + describe '#time_filter_postcodes' do subject(:response) { client.time_filter_postcodes(arrival_searches: []) } diff --git a/spec/travel_time/middleware/proto_middleware_spec.rb b/spec/travel_time/middleware/proto_middleware_spec.rb new file mode 100644 index 0000000..47e3f23 --- /dev/null +++ b/spec/travel_time/middleware/proto_middleware_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.describe TravelTime::Middleware::ProtoMiddleware do + let(:faraday_env) do + Faraday::Env.new.tap do |env| + env.request_headers = Faraday::Utils::Headers.new + end + end + let(:middleware) { described_class.new } + + it 'adds basic auth header' do + middleware.on_request(faraday_env) + value = faraday_env.request_headers['Authorization'] + expect(value).to be_truthy + end + + it 'automatically adds Accept type header' do + middleware.on_request(faraday_env) + value = faraday_env.request_headers['Accept'] + expected = 'application/octet-stream' + expect(value).to eq(expected) + end + + it 'automatically adds Content-Type type header' do + middleware.on_request(faraday_env) + value = faraday_env.request_headers['Content-Type'] + expected = 'application/octet-stream' + expect(value).to eq(expected) + end +end diff --git a/spec/travel_time/proto/travel_time/proto_utils_spec.rb b/spec/travel_time/proto/travel_time/proto_utils_spec.rb new file mode 100644 index 0000000..236b1b6 --- /dev/null +++ b/spec/travel_time/proto/travel_time/proto_utils_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe TravelTime::ProtoUtils do + let(:utils) { described_class } + + describe '.encode_fixed_point' do + it 'calculates fixed point correctly' do + expected = -11 + departure_lat = 51.508930 + destination_lat = 51.508824 + expect(utils.encode_fixed_point(departure_lat, destination_lat)).to eq(expected) + end + + it 'builds deltas correctly' do + expected = [-11, -3571] + departure = { lat: 51.508930, lng: -0.131387 } + destinations = [{ lat: 51.508824, lng: -0.167093 }] + expect(utils.build_deltas(departure, destinations)).to eq(expected) + end + end +end diff --git a/travel_time.gemspec b/travel_time.gemspec index 3ecc198..48a3063 100644 --- a/travel_time.gemspec +++ b/travel_time.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'dry-configurable', '~> 0.14.0' spec.add_dependency 'faraday', '>= 1.10', '< 3.0' + spec.add_dependency 'google-protobuf', '>= 3.21', '< 3.21.9' # Specify which files should be added to the gem when it is released. spec.files = Dir['{bin,lib}/**/*', 'LICENSE.md', 'Rakefile', 'README.md']