routing 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/Gemfile +8 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +54 -0
- data/Rakefile +10 -0
- data/lib/routing.rb +77 -0
- data/lib/routing/adapter.rb +22 -0
- data/lib/routing/adapter/navteq.rb +83 -0
- data/lib/routing/adapter/test.rb +24 -0
- data/lib/routing/geo_point.rb +34 -0
- data/lib/routing/middleware.rb +29 -0
- data/lib/routing/parser.rb +26 -0
- data/lib/routing/parser/navteq_simple.rb +112 -0
- data/lib/routing/version.rb +3 -0
- data/routing.gemspec +24 -0
- data/spec/fixtures/navteq/error_response.json +1 -0
- data/spec/fixtures/navteq/response.json +357 -0
- data/spec/routing/adapter/navteq_spec.rb +59 -0
- data/spec/routing/adapter/test_spec.rb +29 -0
- data/spec/routing/geo_point_spec.rb +40 -0
- data/spec/routing/parser/navteq_simple_spec.rb +81 -0
- data/spec/routing_spec.rb +82 -0
- data/spec/spec_helper.rb +23 -0
- metadata +113 -0
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.navteq_host
|
6
|
+
.yardoc
|
7
|
+
.rspec
|
8
|
+
.rvmrc
|
9
|
+
Gemfile.lock
|
10
|
+
InstalledFiles
|
11
|
+
_yardoc
|
12
|
+
coverage
|
13
|
+
doc/
|
14
|
+
lib/bundler/man
|
15
|
+
pkg
|
16
|
+
rdoc
|
17
|
+
spec/reports
|
18
|
+
test/tmp
|
19
|
+
test/version_tmp
|
20
|
+
tmp
|
21
|
+
**/.DS_Store
|
22
|
+
.DS_Store
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 flinc AG
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Routing
|
2
|
+
|
3
|
+
Provides a generic interface for routing services that can by used to calculate directions between geolocations.
|
4
|
+
|
5
|
+
It aims to make parsing and use-case specific data handling easy trough an extendable middleware stack (think of rack middleware for your routing service).
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
start = Routing::GeoPoint.new(:lat => 49, :lng => 9)
|
11
|
+
destination = Routing::GeoPoint.new(:lat => 48, :lng => 8.9)
|
12
|
+
|
13
|
+
route = Routing.new.calculate(start, destination)
|
14
|
+
```
|
15
|
+
|
16
|
+
### Middleware
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
routing = Routing.new do |r|
|
20
|
+
r.use MyMiddleware.new
|
21
|
+
r.use MyRoutingCache.new
|
22
|
+
end
|
23
|
+
|
24
|
+
route = routing.calculate(start, destination)
|
25
|
+
```
|
26
|
+
|
27
|
+
### Custom Adapters
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
routing = Routing.new(MyAdapter.new)
|
31
|
+
route = routing.calculate(start, destination)
|
32
|
+
```
|
33
|
+
|
34
|
+
## Installation
|
35
|
+
|
36
|
+
Add this line to your application's Gemfile:
|
37
|
+
|
38
|
+
gem 'routing'
|
39
|
+
|
40
|
+
And then execute:
|
41
|
+
|
42
|
+
$ bundle
|
43
|
+
|
44
|
+
Or install it yourself as:
|
45
|
+
|
46
|
+
$ gem install routing
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
1. Fork it
|
51
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
52
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
53
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
54
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/routing.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require "routing/version"
|
2
|
+
|
3
|
+
# The {Routing} class is the main entry point for the library.
|
4
|
+
class Routing
|
5
|
+
|
6
|
+
autoload :Adapter, "routing/adapter"
|
7
|
+
autoload :GeoPoint, "routing/geo_point"
|
8
|
+
autoload :Middleware, "routing/middleware"
|
9
|
+
autoload :Parser, "routing/parser"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Sets the default adapter/routing service.
|
14
|
+
#
|
15
|
+
# @return [Object] Default adapter.
|
16
|
+
attr_writer :default_adapter
|
17
|
+
|
18
|
+
# The default adapter/routing service that is used, if no one is specified.
|
19
|
+
# Currently this is {Routing::Adapter::Navteq}.
|
20
|
+
#
|
21
|
+
# @return [Object] Current default adapter.
|
22
|
+
def default_adapter
|
23
|
+
@default_adapter ||= Routing::Adapter::Navteq.new
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :middlewares
|
29
|
+
|
30
|
+
# Creates a new instance of the routing class
|
31
|
+
#
|
32
|
+
# @param [Object] adapter Adapter for the routing service that should be used, defaults to {Routing::Adapter::Navteq}.
|
33
|
+
def initialize(adapter = self.class.default_adapter)
|
34
|
+
@adapter = adapter
|
35
|
+
@middlewares = []
|
36
|
+
|
37
|
+
yield(self) if block_given?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calculates a route for the passed {GeoPoint}s.
|
41
|
+
# These will be passed through the middleware stack and will
|
42
|
+
# finally be given to the configured adapter which calculates a route out of it.
|
43
|
+
#
|
44
|
+
# @param [Array<GeoPoint>] geo_points
|
45
|
+
# An array of geo points to calculate a route of.
|
46
|
+
#
|
47
|
+
# @return [Array<GeoPoint>]
|
48
|
+
# An array of geo points that represent the calculated route.
|
49
|
+
def calculate(*geo_points)
|
50
|
+
calculate_with_stack(geo_points.flatten, middlewares + [@adapter])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Adds an object to the middleware stack.
|
54
|
+
#
|
55
|
+
# @param [Routing::Middleware] middleware The middleware to append to the stack.
|
56
|
+
#
|
57
|
+
# @return [Array<Routing::Middleware>] Updated list of used middlewares.
|
58
|
+
def use(middleware)
|
59
|
+
@middlewares << middleware
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Helper method that will iterate through the middleware stack.
|
65
|
+
#
|
66
|
+
# @param [Array<GeoPoint>] geo_points
|
67
|
+
# The array of geo points to be passed to the middleware.
|
68
|
+
#
|
69
|
+
# @param [Routing::Middleware] stack
|
70
|
+
# The remaining stack of middlewares to iterate through.
|
71
|
+
def calculate_with_stack(geo_points, stack)
|
72
|
+
stack.shift.calculate(geo_points) do |gp|
|
73
|
+
calculate_with_stack(gp, stack)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Routing
|
2
|
+
|
3
|
+
# The {Routing::Adapter} namespace holds classes that are responsible to accept an
|
4
|
+
# array of {Routing::GeoPoint}s and calculate a route based on these positions.
|
5
|
+
#
|
6
|
+
# Most commonly they will get the calculated route from a webservice and will create a compatible
|
7
|
+
# return value by using a matching {Routing::Parser}.
|
8
|
+
#
|
9
|
+
# The end-point of the middleware stack is always the chosen adapter of a {Routing} instance which has to return the
|
10
|
+
# calculated route in form of {Routing::GeoPoint} objects again.
|
11
|
+
#
|
12
|
+
# Creating your own adapter:
|
13
|
+
# To connect your own routing service, you can easily implement your own adapter.
|
14
|
+
# The only requirements are an instance method called *calculate* that will take an Array of {Routing::GeoPoint}s
|
15
|
+
# as only parameters and will return an Array of {Routing::GeoPoint}s when the calculation is done.
|
16
|
+
module Adapter
|
17
|
+
|
18
|
+
autoload :Navteq, "routing/adapter/navteq"
|
19
|
+
autoload :Test, "routing/adapter/test"
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'faraday'
|
3
|
+
|
4
|
+
class Routing
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# Adapter for a NAVTEQ LBSP Routing Service v6 server.
|
8
|
+
# It passes the {GeoPoint}s to the routing service and will return another
|
9
|
+
# Array of {GeoPoint}s, representing the calculated route.
|
10
|
+
class Navteq
|
11
|
+
|
12
|
+
ATTR_ACCESSIBLE = [ :host, :default_params ]
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
options.each do |attribute, value|
|
16
|
+
send("#{attribute}=", value) if ATTR_ACCESSIBLE.include?(attribute)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def calculate(geo_points)
|
21
|
+
parse get(geo_points)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(geo_points)
|
25
|
+
params = default_params.merge geo_points_to_params(geo_points)
|
26
|
+
|
27
|
+
response = connection.get do |request|
|
28
|
+
request.url(service_path)
|
29
|
+
request.params = params
|
30
|
+
end
|
31
|
+
|
32
|
+
response.body
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_writer :host
|
36
|
+
|
37
|
+
def host
|
38
|
+
@host
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(response)
|
42
|
+
::Routing::Parser::NavteqSimple.new(response).to_geo_points
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_writer :default_params
|
46
|
+
|
47
|
+
def default_params
|
48
|
+
@default_params || {
|
49
|
+
departure: Time.now.utc.iso8601,
|
50
|
+
mode0: "fastest;car",
|
51
|
+
language: "de_DE",
|
52
|
+
legattributes: "all,-links",
|
53
|
+
maneuverattributes: "position,travelTime,length,time"
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
# The path on the server to the routing service.
|
60
|
+
# This is determined by the server and should not be changed.
|
61
|
+
#
|
62
|
+
# @returns [String]
|
63
|
+
# Path to the routing service.
|
64
|
+
def service_path
|
65
|
+
"/routing/6.2/calculateroute.json"
|
66
|
+
end
|
67
|
+
|
68
|
+
def geo_points_to_params(geo_points)
|
69
|
+
Hash[geo_points.each_with_index.map { |point, i| [ "waypoint#{i}", "geo!#{point.lat},#{point.lng}" ] }]
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection
|
73
|
+
@connection ||= Faraday.new(:url => host) do |builder|
|
74
|
+
builder.request :url_encoded
|
75
|
+
builder.response :logger
|
76
|
+
builder.adapter :net_http
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Routing
|
2
|
+
module Adapter
|
3
|
+
|
4
|
+
# Simple test adapter, that returns an array of {GeoPoint}s
|
5
|
+
# with values which are based on the ones it recieved.
|
6
|
+
class Test
|
7
|
+
|
8
|
+
def calculate(geo_points)
|
9
|
+
geo_points.collect do |point|
|
10
|
+
GeoPoint.new(
|
11
|
+
:lat => point.lat,
|
12
|
+
:lng => point.lng,
|
13
|
+
:original_lat => point.lat,
|
14
|
+
:original_lng => point.lng,
|
15
|
+
:relative_time => 100,
|
16
|
+
:distance => 100,
|
17
|
+
:waypoint => true
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Routing
|
2
|
+
|
3
|
+
# A {GeoPoint} object is a very simple representation of a geospatial point
|
4
|
+
# that is part of a route or can be used as base to create one.
|
5
|
+
#
|
6
|
+
# It holds the most basic textual and geospatial information that is necessary
|
7
|
+
# to describe a section of a route.
|
8
|
+
#
|
9
|
+
# The complete roundtrip from a {Routing} object through the {Routing::Middleware} to a
|
10
|
+
# {Routing::Adapter} and back uses arrays of {GeoPoint} objects as arguments.
|
11
|
+
#
|
12
|
+
# Depending on your very own use-case, you can either extend the {GeoPoint} class or
|
13
|
+
# use another class that mimicks the behavior of {GeoPoint}.
|
14
|
+
# In fact, this is just a container class which is no hard requirement in general.
|
15
|
+
# If you decide to roll your own {Adapter}, {Parser} and maybe {Middleware},
|
16
|
+
# you could replace the class completely
|
17
|
+
class GeoPoint < Struct.new(:lat, :lng, :original_lat, :original_lng, :relative_time, :distance, :waypoint, :type)
|
18
|
+
|
19
|
+
# Creates a new GeoPoint instance.
|
20
|
+
#
|
21
|
+
# @param [Hash] attributes
|
22
|
+
# Automatically sets values for the attributes that match the keys of the hash.
|
23
|
+
def initialize(attributes = {})
|
24
|
+
attributes.each do |attribute, value|
|
25
|
+
self[attribute] = value if members.include?(attribute)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def waypoint?
|
30
|
+
!!waypoint
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Routing
|
2
|
+
# An abstract middleware class to illustrate how you can build your own.
|
3
|
+
#
|
4
|
+
# @abstract
|
5
|
+
class Middleware
|
6
|
+
|
7
|
+
# The only method your own middleware has to implement to work.
|
8
|
+
#
|
9
|
+
# @param [Array<GeoPoint>] geo_points
|
10
|
+
# The array of {GeoPoint}s that should be routed.
|
11
|
+
#
|
12
|
+
# @yield [Array<GeoPoint>]
|
13
|
+
# Passes the geo_points on to the next middleware in the stack.
|
14
|
+
# Please note, that you _always_ have to call yield in order to make
|
15
|
+
# the middleware stack proceed.
|
16
|
+
#
|
17
|
+
# If you will return a value before yielding, you will cancel the rest of the middleware stack.
|
18
|
+
#
|
19
|
+
# @return [Array<GeoPoint>]
|
20
|
+
# The array of {GeoPoint}s after they have been routed.
|
21
|
+
def calculate(geo_points)
|
22
|
+
# manipulate geo points before routing here
|
23
|
+
yield(geo_points) # hand control to the next middleware in the stack
|
24
|
+
# manipulate geo points after routing here
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Routing
|
2
|
+
# The {Routing::Parser} namespace holds classes that are used to parse the
|
3
|
+
# response of a specific routing service.
|
4
|
+
#
|
5
|
+
# They are used by the matching adapters {Routing::Adapter} to create the array of {Routing::GeoPoint} objects
|
6
|
+
# to pass into the middleware.
|
7
|
+
module Parser
|
8
|
+
|
9
|
+
autoload :NavteqSimple, "routing/parser/navteq_simple"
|
10
|
+
|
11
|
+
# This error is thrown by the parsers if a calculated waypoint can't be matched
|
12
|
+
# to one of the initial passed geo points that were routed.
|
13
|
+
#
|
14
|
+
# The reason why this is important is that most routing services have a "snap-to-route"
|
15
|
+
# algorithm that will manipulate the coordinates of a passed geo point by moving it to
|
16
|
+
# the next routeable position.
|
17
|
+
# A parser should be able to determine which geo point of the response belongs to which geo point of the
|
18
|
+
# request. Otherwise this error will be thrown.
|
19
|
+
class NoMatchingMappedPositionFound < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class RoutingFailed < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class Routing
|
2
|
+
module Parser
|
3
|
+
|
4
|
+
# A very simple parser implementation for a NAVTEQ LBSP Routing Service v6.
|
5
|
+
# It converts the json response of the routing service to an Array of {GeoPoint}s.
|
6
|
+
class NavteqSimple
|
7
|
+
|
8
|
+
# Creates a new instance of the parser.
|
9
|
+
#
|
10
|
+
# @param [String] response
|
11
|
+
# A json response string of a NAVTEQ routing server.
|
12
|
+
def initialize(response)
|
13
|
+
response = JSON.parse(response)
|
14
|
+
check_for_error(response)
|
15
|
+
|
16
|
+
@route = response["Response"]["Route"].first
|
17
|
+
@overall_covered_distance = 0
|
18
|
+
@overall_relative_time = 0
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts the server response in an Array of {GeoPoint}s
|
24
|
+
#
|
25
|
+
# @return [Array<GeoPoint>]
|
26
|
+
# List of {GeoPoint}s that represent the calculated route.
|
27
|
+
def to_geo_points
|
28
|
+
legs = @route["Leg"]
|
29
|
+
geo_points = legs.map { |leg| parse_leg(leg) }.flatten
|
30
|
+
|
31
|
+
# At last we add the destination point
|
32
|
+
geo_points << parse_maneuver(legs.last["Maneuver"].last, waypoint: true)
|
33
|
+
geo_points
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Parses is single leg of the route including all its maneuvers.
|
39
|
+
#
|
40
|
+
# @param [Hash] leg
|
41
|
+
# The route leg to parse.
|
42
|
+
#
|
43
|
+
# @return [Array<GeoPoint>]
|
44
|
+
# List of {GeoPoint}s that represent the passed Leg.
|
45
|
+
def parse_leg(leg)
|
46
|
+
# Skip the last maneuver as it is the same as the first one
|
47
|
+
# of the next maneuver.
|
48
|
+
# For the last leg we parse the last maneuver right at the end
|
49
|
+
maneuvers = leg["Maneuver"][0...-1]
|
50
|
+
maneuvers.map do |maneuver|
|
51
|
+
parse_maneuver(maneuver, :waypoint => (maneuver == maneuvers.first))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Parses is single maneuver of a route leg.
|
56
|
+
#
|
57
|
+
# @param [Hash] maneuver
|
58
|
+
# The maneuver to parse.
|
59
|
+
#
|
60
|
+
# @param [Hash] attributes
|
61
|
+
# Additional attributes that should be set on the returned {GeoPoint}.
|
62
|
+
#
|
63
|
+
# @return [GeoPoint]
|
64
|
+
# A {GeoPoint} that represents the passed maneuver.
|
65
|
+
def parse_maneuver(maneuver, attributes = {})
|
66
|
+
geo_point = ::Routing::GeoPoint.new attributes.merge({
|
67
|
+
lat: maneuver["Position"]["Latitude"],
|
68
|
+
lng: maneuver["Position"]["Longitude"],
|
69
|
+
relative_time: @overall_relative_time,
|
70
|
+
distance: @overall_covered_distance
|
71
|
+
})
|
72
|
+
|
73
|
+
@overall_relative_time += maneuver["TravelTime"].to_i
|
74
|
+
@overall_covered_distance += maneuver["Length"].to_i
|
75
|
+
|
76
|
+
search_original_position(geo_point) if geo_point.waypoint?
|
77
|
+
|
78
|
+
geo_point
|
79
|
+
end
|
80
|
+
|
81
|
+
# Matches a parsed {GeoPoint} of the route response
|
82
|
+
# with the (unmapped) position of the
|
83
|
+
# corresponding {GeoPoint} of the request.
|
84
|
+
#
|
85
|
+
# @param [GeoPoint] geo_point
|
86
|
+
# Point of the response to find the initial position for.
|
87
|
+
#
|
88
|
+
# @return [GeoPoint]
|
89
|
+
# The passed in {GeoPoint}, enriched with the information about the original position in the request.
|
90
|
+
#
|
91
|
+
# @raise [NoMatchingMappedPositionFound] If no matching original position is found.
|
92
|
+
def search_original_position(geo_point)
|
93
|
+
matching_waypoint = @route["Waypoint"].detect do |waypoint|
|
94
|
+
waypoint["MappedPosition"]["Latitude"] == geo_point.lat &&
|
95
|
+
waypoint["MappedPosition"]["Longitude"] == geo_point.lng
|
96
|
+
end or raise NoMatchingMappedPositionFound
|
97
|
+
|
98
|
+
geo_point.original_lat = matching_waypoint["OriginalPosition"]["Latitude"]
|
99
|
+
geo_point.original_lng = matching_waypoint["OriginalPosition"]["Longitude"]
|
100
|
+
|
101
|
+
geo_point
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_for_error(response)
|
105
|
+
if error = response['Error']
|
106
|
+
raise Routing::Parser::RoutingFailed.new("#{error['type']}(#{error['subtype']}) - #{error['Details']}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|