gdsapi-v2-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 90763d8f75b2b2da576237c0ff6122f01ea0879d
4
+ data.tar.gz: e168a1a22eefe7b25ae71fb10192b3353b067eb1
5
+ SHA512:
6
+ metadata.gz: 8e0b8a2eaba2d2519eae8c5d64cca73d8ac657040b8708e2cd15d4562fefd9edaa26c6a812a80aaca5a36947086786b51b1beb9d54ee7039a74b600aca0cd85e
7
+ data.tar.gz: a3cfaff0d88682f5191e28ced0b06c7123b619bc3e4daeff81f90b99f99c4ba27b51460042f3e0dd0440baf275c3ee83a2cc3d9b162c55a62d4cd53b4a6d3a0a
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ credentials.json
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gdsapi-v2-ruby.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Gdsapi::V2::Ruby
2
+ Driver for API.GDS.BUSFOR v2
3
+
4
+ # Development usage
5
+ 1) Run
6
+ ```bash
7
+ bin/setup
8
+ ```
9
+ 2) Fill in fields in credentials.json
10
+ 3) Run
11
+ ```bash
12
+ bin/console
13
+ ```
14
+ Now you can use client instance for querying GDS
15
+
16
+ # How to use Client
17
+ ## Create Gdsapi::V2::Client instance
18
+ (NOTE: you can use method #client in bin/console)
19
+ ### Using Faraday
20
+ Suppose you want to use Faraday as HTTP Driver. So to instantiate client, you can use Gdsapi::V2::Client#with_faraday_requester method:
21
+ ```ruby
22
+ Gdsapi::V2::Client.with_faraday_requester base_url: 'https://example.com', login: 'login', password: 'password', language: 'en'
23
+ => #<Gdsapi::V2::Client:0x007fe3bf1e8d68 @requester=#<Gdsapi::V2::Requester:0x007fe3bf1e9330 @driver=#<Faraday::Connection:0x007fe3bf1eafc8 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v0.12.1", "Accept-Language"=>"en", "Authorization"=>"Basic bG9naW46cGFzc3dvcmQ="}, @params={}, @options=#<Faraday::RequestOptions (empty)>, @ssl=#<Faraday::SSLOptions (empty)>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x007fe3bf1ea640 @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, @url_prefix=#<URI::HTTPS https://example.com/>, @proxy=nil>, @login="login", @password="password", @language="en">>
24
+ ```
25
+ available arguments are:
26
+ - :base_url - url param for Faraday(requred)
27
+ - :login & :password - HTTP basic auth factors
28
+ - :language: - language slug for which request will be performed (available are: en, ru, uk, pl, th)
29
+ ### Using custom driver
30
+ If you want to use custom driver, you can do Gdsapi::V2::Client#with_faraday_requester method:
31
+ ```ruby
32
+ driver_instance = Faraday.new url: 'https://example.com'
33
+ => #<Faraday::Connection:0x007fe3bf18be88 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v0.12.1"}, @params={}, @options=#<Faraday::RequestOptions (empty)>, @ssl=#<Faraday::SSLOptions (empty)>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x007fe3bf189c28 @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, @url_prefix=#<URI::HTTPS https://example.com/>, @proxy=nil>
34
+ Gdsapi::V2::Client.with_requester driver_instance, login: 'login', password: 'password', language: 'en'
35
+ => #<Gdsapi::V2::Client:0x007fe3c07172e8 @requester=#<Gdsapi::V2::Requester:0x007fe3c07176f8 @driver=#<Faraday::Connection:0x007fe3bf18be88 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v0.12.1", "Accept-Language"=>"en", "Authorization"=>"Basic bG9naW46cGFzc3dvcmQ="}, @params={}, @options=#<Faraday::RequestOptions (empty)>, @ssl=#<Faraday::SSLOptions (empty)>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x007fe3bf189c28 @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, @url_prefix=#<URI::HTTPS https://example.com/>, @proxy=nil>, @login="login", @password="password", @language="en">>
36
+ ```
37
+ available keyword arguments are:
38
+ - :login & :password - HTTP basic auth factors
39
+ - :language - language slug for which request will be performed (available are: en, ru, uk, pl, th)
40
+
41
+ `driver` instance should respond to:
42
+ - `get(url, params, headers)`
43
+ = `post(url, body, params)`
44
+ - `basic_auth(login, password)`
45
+ - querying and modifying #headers in Hash-style
46
+
47
+ ## Use Gdsapi::V2::Client instance
48
+ ### #get_locations
49
+
50
+ Fetch GDS locations edited after given timestamp. If timestamp is not given, all results are returned in 'upsert' mode.
51
+
52
+ Returns hash with :upsert and :delete keys, for update or inserting mode and deleting mode respectively.
53
+ Values for hash are Location objects.
54
+
55
+ For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get]
56
+
57
+ keyword args:
58
+ - :country [Number|String] - GDS country id for which locations are fetched (optional)
59
+ - :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
60
+ - :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
61
+ - :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested) (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT+2)
62
+
63
+ ```ruby
64
+ client.get_locations limit: 5, offset: 15
65
+ => {:upsert=>[#<Gdsapi::V2::Structs::Location id="3642" name="Кречев" latitude=50.67921380000001 longitude=24.0974334 parent={:id=>"1330"} type={:id=>"6", :name=>"населённый пункт"} sub_type={:id=>"10", :name=>"с."} attributes=[{:id=>"6", :name=>"KOATUU (Ukraine)", :values=>["0721183202"]}]>, #<Gdsapi::V2::Structs::Location id="3643" name="Морозовичи" latitude=50.7066762 longitude=24.1291352 parent={:id=>"1330"} type={:id=>"6", :name=>"населённый пункт"} sub_type={:id=>"10", :name=>"с."} attributes=[{:id=>"6", :name=>"KOATUU (Ukraine)", :values=>["0721183301"]}]>, #<Gdsapi::V2::Structs::Location id="3644" name="Волица-Морозовицкая" latitude=50.7207646 longitude=24.1088641 parent={:id=>"1330"} type={:id=>"6", :name=>"населённый пункт"} sub_type={:id=>"10", :name=>"с."} attributes=[{:id=>"6", :name=>"KOATUU (Ukraine)", :values=>["0721183303"]}]>, #<Gdsapi::V2::Structs::Location id="3645" name="Русовичи" latitude=50.7196979 longitude=24.1388295 parent={:id=>"1330"} type={:id=>"6", :name=>"населённый пункт"} sub_type={:id=>"10", :name=>"с."} attributes=[{:id=>"6", :name=>"KOATUU (Ukraine)", :values=>["0721183305"]}]>, #<Gdsapi::V2::Structs::Location id="3646" name="Мышев" latitude=50.6814711 longitude=24.3183851 parent={:id=>"1330"} type={:id=>"6", :name=>"населённый пункт"} sub_type={:id=>"10", :name=>"с."} attributes=[{:id=>"6", :name=>"KOATUU (Ukraine)", :values=>["0721183401"]}]>]}
66
+ ```
67
+
68
+ ### #get_points
69
+
70
+ Fetch GDS points edited after given timestamp. If timestamp is not given, all results are returned in 'upsert' mode.
71
+
72
+ Returns hash with :upsert and :delete keys, for update or inserting mode and deleting mode respectively.
73
+ Values for hash are Point objects.
74
+
75
+ For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get]
76
+
77
+ keyword args:
78
+ - :country [Number|String] - GDS country id for which points are fetched (optional)
79
+ - :location [Number|String] - GDS location id for which points are fetched (optional)
80
+ - :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
81
+ - :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
82
+ - :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested) (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT+2)
83
+
84
+ ```ruby
85
+ client.get_points limit: 5, offset: 5
86
+ => {:upsert=>[#<Gdsapi::V2::Structs::Point id="981165" name="Остановка Патинская" latitude=61.577575 longitude=46.304784 parent={:id=>"72788"} address="Остановка Патинская">, #<Gdsapi::V2::Structs::Point id="981166" name="Остановка Перекоп" latitude=61.589032 longitude=46.372445 parent={:id=>"72789"} address="Остановка Перекоп">, #<Gdsapi::V2::Structs::Point id="981157" name="Остановка Микшино" latitude=61.582234 longitude=46.355782 parent={:id=>"72780"} address="Остановка Микшино">, #<Gdsapi::V2::Structs::Point id="981158" name="Остановка Мичкинская" latitude=61.619846 longitude=46.475554 parent={:id=>"7278
87
+ ```
88
+
89
+
90
+ ### #get_countries
91
+
92
+ Fetch GDS countries
93
+
94
+ Returns set of Country objects
95
+
96
+ keyword args:
97
+ - :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
98
+ - :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
99
+
100
+ ```ruby
101
+ client.get_countries limit: 5, offset: 10
102
+ => [#<Gdsapi::V2::Structs::Country id="350" name="Пуэрто-Рико" type={:id=>"1", :name=>"страна"} attributes=[{:id=>"2", :name=>"globus_locality_id", :values=>["22160"]}, {:id=>"5", :name=>"FIPS_Code", :values=>["RQ"]}]>, #<Gdsapi::V2::Structs::Country id="351" name="Реюньон" type={:id=>"1", :name=>"страна"} attributes=[{:id=>"2", :name=>"globus_locality_id", :values=>["22084"]}]>, #<Gdsapi::V2::Structs::Country id="352" name="Сен-Бартелеми" type={:id=>"1", :name=>"страна"} attributes=[{:id=>"2", :name=>"globus_locality_id", :values=>["27236"]}, {:id=>"5", :name=>"FIPS_Code", :values=>["TB"]}]>, #<Gdsapi::V2::Structs::Country id="110" name="Мали" type={:id=>"1", :name=>"страна"} attributes=[{:id=>"2", :name=>"globus_locality_id", :values=>["22342"]}, {:id=>"3", :name=>"GENC_2A_Code", :values=>["ML"]}, {:id=>"4", :name=>"GENC_3A_Code", :values=>["MLI"]}]>, #<Gdsapi::V2::Structs::Country id="353" name="О́стров Свято́й Еле́ны" type={:id=>"1", :name=>"страна"} attributes=[{:id=>"2", :name=>"globus_locality_id", :values=>["27895"]}, {:id=>"5", :name=>"FIPS_Code", :values=>["SH"]}]>]
103
+ ```
104
+
105
+ ### #get_available trips
106
+
107
+ Check Availability of trips
108
+
109
+ Returns set of available dates for specified trip
110
+
111
+ keyword args:
112
+ - :arrival [String] - GDS arrival point id (required)
113
+ - :departure [String] - GDS departure point id (required)
114
+ - :tickets [Number] - number of tickets for trip (required)
115
+ - :mode [Number] - transport type (optional, buses are: 1,8)
116
+ - :dates [String] - dates for trip (required, format: yyyy-mm-dd, delimiter: ';')
117
+ - :return_only_branded [Boolean] - returns data only for branded trips if true
118
+
119
+ ```ruby
120
+ client.get_available_trips arrival: 'A7CD147C07E7CD1BE040A8C0630328EC',
121
+ departure: 'CA975BC2ACDBCF7FE040B859C14327B3',
122
+ tickets: 2,
123
+ dates: '2017-06-22;2017-06-23'
124
+ => ["2017-06-22", "2017-06-23"]
125
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gdsapi"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ Bundler.require :default, :development
15
+ creds = JSON.parse(File.read('credentials.json'))
16
+ @login = creds['login']
17
+ @password = creds['password']
18
+ @host = creds['host']
19
+
20
+ def driver
21
+ Faraday.new url: @host
22
+ end
23
+
24
+ def requester
25
+ Gdsapi::V2::Requester.new driver, login: @login, password: @password
26
+ end
27
+
28
+ def client
29
+ Gdsapi::V2::Client.new requester
30
+ end
31
+
32
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ bundle install
3
+
4
+ # Do any other automated setup that you need to do here
5
+ cp "credentials.json.sample" "credentials.json"
@@ -0,0 +1,5 @@
1
+ {
2
+ "login": "",
3
+ "password": "",
4
+ "host": ""
5
+ }
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gdsapi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gdsapi-v2-ruby"
8
+ spec.version = Gdsapi::V2::VERSION
9
+ spec.authors = ["Mike Yurchenkov"]
10
+ spec.email = ["mikesehse@gmail.com"]
11
+
12
+ spec.summary = %q{Driver for API.GDS.BUSFOR v2}
13
+ spec.homepage = "https://busfor.ru"
14
+ spec.license = "MIT"
15
+
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.require_paths = ["lib"]
21
+ spec.add_dependency "json", ">= 1.8"
22
+ spec.add_dependency "dry-struct", "~> 0.3"
23
+ spec.add_dependency "faraday", ">= 0.9"
24
+
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.14"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.6"
29
+ spec.add_development_dependency "webmock", "~> 3.0"
30
+ spec.add_development_dependency "rubocop", "~> 0.49"
31
+ spec.add_development_dependency "faraday", ">= 0.9"
32
+ end
data/lib/gdsapi.rb ADDED
@@ -0,0 +1,22 @@
1
+ Dir[File.expand_path('gdsapi/**/*.rb', __dir__)].each { |file| require file }
2
+
3
+ # This provides core package manifesto
4
+ ## Possible error definitions are provided here
5
+ module Gdsapi
6
+ module V2
7
+ # Error thrown on something done wrong on request-side
8
+ class BadRequestError < StandardError ; end
9
+ # Error thrown on 401 from GDS
10
+ class UnathorizedError < BadRequestError ; end
11
+ # Error thrown on 404 from GDS
12
+ class NotFoundError < BadRequestError ; end
13
+ # Error thrown when something is weird with GDS
14
+ class GdsError < StandardError ; end
15
+ # Error thrown when Gds returned server-side exception
16
+ class GdsInternalError < GdsError ; end
17
+ # Error thrown when GDS returned sudden redirect
18
+ class GdsRedirectError < GdsError ; end
19
+ # Error thrown when GDS returned somewhat invalid
20
+ class MalformedGdsResponse < GdsError ; end
21
+ end
22
+ end
@@ -0,0 +1,85 @@
1
+ # Facade class for performing GDS requests
2
+ module Gdsapi
3
+ module V2
4
+ class Client
5
+ attr_reader :requester
6
+
7
+ # @param [Gdsapi::V2::Requester] requester - requester instance
8
+ # It is `strongly recommended` to use Faraday(see ##with_faraday_requester)
9
+ def initialize(requester)
10
+ @requester = requester
11
+ end
12
+
13
+ class << self
14
+ # Constructing routine that uses Faraday as driver
15
+ # @param base_url: url param for Faraday(requred)
16
+ # @param login: & password: - HTTP basic auth factors
17
+ # @param language: - language slug for which request will be performed (available are: en, ru, uk, pl, th)
18
+ def with_faraday_requester(base_url: nil, login: nil, password: nil, language: nil)
19
+ driver = Faraday.new(url: base_url)
20
+ new(Requester.new(driver, login: login, password: password, language: language))
21
+ end
22
+
23
+ # Constructing routine that uses given driver
24
+ # @param driver - driver instance
25
+ # @param login: & password: - HTTP basic auth factors
26
+ # @param language: - language slug for which request will be performed (available are: en, ru, uk, pl, th)
27
+ def with_requester(driver, login: nil, password: nil, language: nil)
28
+ new(Requester.new(driver, login: login, password: password, language: language))
29
+ end
30
+ end
31
+
32
+ # Ad-hoc solution for adding multilanguage support
33
+ def language=(value)
34
+ requester.language = value
35
+ end
36
+
37
+ # GDS Method used to fetch GDS locations
38
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get]
39
+ # @param args - keyword args:
40
+ # :country [Number|String] - GDS country id for which locations are fetched (optional)
41
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
42
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
43
+ # :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested)
44
+ # (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT+2)
45
+ def get_locations(**args)
46
+ Methods::GetLocations.new(requester).call(**args)
47
+ end
48
+
49
+ # GDS Method used to fetch GDS points
50
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get]
51
+ # @param args - keyword args:
52
+ # :country [Number|String] - GDS country id for which points are fetched (optional)
53
+ # :location [Number|String] - GDS location id for which points are fetched (optional)
54
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
55
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
56
+ # :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested)
57
+ # (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT+2)
58
+ def get_points(**args)
59
+ Methods::GetPoints.new(requester).call(**args)
60
+ end
61
+
62
+ # GDS Method used to fetch GDS countries
63
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-страны-get]
64
+ # @param args - keyword args:
65
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
66
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
67
+ def get_countries(**args)
68
+ Methods::GetCountries.new(requester).call(**args)
69
+ end
70
+
71
+ # GDS Method used to check availability of trips
72
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#поиск-рейсов-доступность-рейсов-get]
73
+ # @param args - keyword args:
74
+ # :arrival [String] - GDS arrival point id (required)
75
+ # :departure [String] - GDS departure point id (required)
76
+ # :tickets [Number] - number of tickets for trip (required)
77
+ # :mode [Number] - transport type (optional, buses are: 1,8)
78
+ # :dates [String] - dates for trip (required, format: yyyy-mm-dd, delimiter: ';')
79
+ # :return_only_branded [Boolean] - returns data only for branded trips if true
80
+ def get_available_trips(**args)
81
+ Methods::GetAvailableTrips.new(requester).call(**args)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,8 @@
1
+ #TODO: Here provide package documentation
2
+ ## This package is used to declare GDS method types
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,104 @@
1
+ require_relative 'utils/parse_utils'
2
+
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ class BaseMethod
7
+ include ParseUtils
8
+ attr_reader :requester
9
+
10
+ # Constructor method for passing requester instance
11
+ # @param [[Gdsapi::V2::Requester]] - requester instance
12
+ def initialize(requester)
13
+ @requester = requester
14
+ end
15
+
16
+ ## Generic entry-point for action
17
+ ## @param hash_styled `list` and `offset` args used for chunking requests
18
+ ## @return - parsed results
19
+ def call(**params)
20
+ response_body = query params
21
+ body = JSON.parse response_body
22
+ parse body
23
+ rescue JSON::ParserError
24
+ raise MalformedGdsResponse, "Invalid JSON: #{response_body}"
25
+ end
26
+
27
+ protected
28
+ # HTTP verb used to query GDS API for method
29
+ # Defaults to :get
30
+ # @return [Symbol] - symbolic alias for requester method corresponding to HTTP verb (i.e., :get or :post)
31
+ def method
32
+ :get
33
+ end
34
+
35
+ # GDS API path for method
36
+ # It is required for method to specify path
37
+ def parse(*args)
38
+ raise NotImplementedError, 'Parsing is not allowed for abstract method'
39
+ end
40
+
41
+ # GDS API path for method
42
+ # It is required for method to specify path
43
+ def path
44
+ raise NotImplementedError, 'Querying is not allowed for abstract method'
45
+ end
46
+
47
+ # Query routine to be executed while performing action
48
+ # @param [Hash] args - args used for querying
49
+ # @return [String] - response body string
50
+ def query(args)
51
+ params = transform_params(slice_params(args))
52
+ response = requester.public_send(method, path, params: params)
53
+ case response.status
54
+ when (404)
55
+ raise NotFoundError, "Not found on #{response.env.url}"
56
+ when (401)
57
+ raise UnathorizedError, "Invalid auth on #{response.env.url}"
58
+ when (400..499)
59
+ raise BadRequestError, "Bad request on #{response.env.url}: #{response.body.to_s}"
60
+ when (500..599)
61
+ raise GdsInternalError, "#{response.status} #{response.body.to_s}"
62
+ when (300..399)
63
+ raise GdsRedirectError, "#{response.status} #{response.headers['Location'].to_s}"
64
+ else
65
+ response.body
66
+ end
67
+ end
68
+
69
+
70
+ # Transform params with accordance to requirements of API
71
+ # @param [Hash[Symbol => Object]] filtered_params - passed params
72
+ # @return [Hash[Symbol => Object]] transformed set of params
73
+ def transform_params(filtered_params)
74
+ filtered_params
75
+ end
76
+
77
+ # Select params which are allowed in querying for the method
78
+ # @param [Hash[Symbol => Object]] params - passed params
79
+ # @return [Hash[Symbol => Object]] params filtered using whitelist (see #permitted_params) and by presence (!nil?)
80
+ def slice_params(params)
81
+ params.select { |name, value| permitted_params.include?(name) && value }
82
+ end
83
+
84
+ # whitelist of permitted params
85
+ # defaults to empty list
86
+ # @return [Array[Symbol]] - params that are permitted for given instance of method
87
+ def permitted_params
88
+ []
89
+ end
90
+
91
+ # Editing geodata modes mapping (GDS -> output)
92
+ # Used when dealing with temporal data (i.e. given timestamps)
93
+ # U (upsert) - update or create record
94
+ # D (delete) - delete record
95
+ def modes
96
+ {
97
+ 'U' => :upsert,
98
+ 'D' => :delete,
99
+ }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ # GDS Method class used to check availability of trips
2
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#поиск-рейсов-доступность-рейсов-get]
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ class GetAvailableTrips < BaseMethod
7
+ private
8
+
9
+ ## Parsing routine to be executed while performing action
10
+ ## Actually, does nothing
11
+ ## @param [Array] body - parsed JSON payload from GDS
12
+ ## @return [Array] set of available dates for given trip
13
+ def parse(body)
14
+ body
15
+ end
16
+
17
+ def path
18
+ 'available_trips'
19
+ end
20
+
21
+ # Query params
22
+ # :arrival [String] - GDS arrival point id (required)
23
+ # :departure [String] - GDS departure point id (required)
24
+ # :tickets [Number] - number of tickets for trip (required)
25
+ # :mode [Number] - transport type (optional, buses are: 1,8)
26
+ # :dates [String] - dates for trip (required, format: yyyy-mm-dd, delimiter: ';')
27
+ # :return_only_branded [Boolean] - returns data only for branded trips if true
28
+ def permitted_params
29
+ %i(arrival departure tickets mode dates return_only_branded)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ # GDS Method class used to fetch GDS countries
2
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-страны-get]
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ class GetCountries < BaseMethod
7
+
8
+ private
9
+
10
+ def path
11
+ 'countries'
12
+ end
13
+
14
+ ## Parsing routine to be executed while performing action
15
+ ## @param [Hash] body - parsed JSON payload from GDS
16
+ ## @return [Array[Gdsapi::V2::Structs::Country]] parsed set of countries
17
+ def parse(body)
18
+ return [] if body.empty?
19
+
20
+ countries = body['countries']
21
+ types = body['types']
22
+ attributes = body['attributes']
23
+ countries.map do |country|
24
+ args = {
25
+ id: country['id'],
26
+ name: country['name'],
27
+ type: types.find { |type| type['id'] == country['type_id'] },
28
+ attributes: match_attributes(country['attributes'], attributes),
29
+ }
30
+ Gdsapi::V2::Structs::Country.new args
31
+ end
32
+ end
33
+
34
+ # Query params
35
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
36
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
37
+ def permitted_params
38
+ %i(limit offset])
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,70 @@
1
+ # GDS Method class used to fetch GDS locations
2
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-населенных-пунктов-get]
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ class GetLocations < BaseMethod
7
+
8
+ private
9
+
10
+ # Parsing routine invoked on JSON response parsed to hash
11
+ # @param [Hash] body - given response hash from GDS
12
+ # @return [Hash[Symbol => Array[Gdsapi::V2::Structs::Location]]] - location objects grouped by editing mode:
13
+ # :upsert - objects inserted or updated since given timestamp
14
+ # :delete - objects deleted since given timestamp
15
+ def parse(body)
16
+ return {} if body.empty?
17
+
18
+ locations = body['locations']
19
+ types = body['types']
20
+ attributes = body['attributes']
21
+ sub_types = body['subTypes']
22
+ grouped_locations = locations.group_by { |location| location['mod'] }
23
+ parsed_locations = grouped_locations.map do |mod, hash_locations|
24
+ parsed_locations = hash_locations.map do |location|
25
+ args = {
26
+ id: location['id'],
27
+ name: location['name'],
28
+ latitude: location['latitude'],
29
+ longitude: location['longitude'],
30
+ parent: location['parent'],
31
+ type: types.find { |type| type['id'] == location['type_id'] },
32
+ sub_type: sub_types.find { |sub_type| sub_type['id'] == location['sub_type_id'] },
33
+ attributes: match_attributes(location['attributes'], attributes),
34
+ }
35
+ Gdsapi::V2::Structs::Location.new args
36
+ end
37
+ [modes[mod], parsed_locations]
38
+ end
39
+ Hash[parsed_locations]
40
+ end
41
+
42
+ def path
43
+ 'locations'
44
+ end
45
+
46
+ def transform_params(filtered_params)
47
+ filtered_params.tap do |params|
48
+ if params[:timestamp]
49
+ if params[:timestamp].respond_to?(:strftime)
50
+ params[:timestamp] = params[:timestamp].strftime('%FT%T%z')
51
+ else
52
+ params[:timestamp] = params[:timestamp].to_s
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Query params
59
+ # :country [Number|String] - GDS country id for which locations are fetched (optional)
60
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
61
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
62
+ # :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested)
63
+ # (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT +2)
64
+ def permitted_params
65
+ %i(country offset limit timestamp)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,68 @@
1
+ # GDS Method class used to fetch GDS locations
2
+ # For more information, see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get]
3
+ module Gdsapi
4
+ module V2
5
+ module Methods
6
+ class GetPoints < BaseMethod
7
+
8
+ private
9
+
10
+ # Parsing routine invoked on JSON response parsed to hash
11
+ # @param [Hash] body - given response hash from GDS
12
+ # @return [Hash[Symbol => Array[Gdsapi::V2::Structs::Point]]] - point objects grouped by editing mode:
13
+ # :upsert - objects inserted or updated since given timestamp
14
+ # :delete - objects deleted since given timestamp
15
+ def parse(body)
16
+ return {} if body.empty?
17
+
18
+ points = body['points']
19
+ attributes = body['attributes']
20
+ grouped_points = points.group_by { |point| point['mod'] }
21
+ parsed_points = grouped_points.map do |mod, hash_points|
22
+ parsed_points = hash_points.map do |point|
23
+ args = {
24
+ id: point['id'],
25
+ name: point['name'],
26
+ latitude: point['latitude'],
27
+ longitude: point['longitude'],
28
+ parent: point['parent'],
29
+ address: point['address'],
30
+ attributes: match_attributes(point['attributes'], attributes),
31
+ }
32
+ Gdsapi::V2::Structs::Point.new args
33
+ end
34
+ [modes[mod], parsed_points]
35
+ end
36
+ Hash[parsed_points]
37
+ end
38
+
39
+ def path
40
+ 'points'
41
+ end
42
+
43
+ def transform_params(filtered_params)
44
+ filtered_params.tap do |params|
45
+ if params[:timestamp]
46
+ if params[:timestamp].respond_to?(:strftime)
47
+ params[:timestamp] = params[:timestamp].strftime('%FT%T%z')
48
+ else
49
+ params[:timestamp] = params[:timestamp].to_s
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Query params
56
+ # :country [Number|String] - GDS country id for which points are fetched (optional)
57
+ # :location [Number|String] - GDS location id for which points are fetched (optional)
58
+ # :offset [Number|String] - number of records to be skipped when fetching chunks (optional)
59
+ # :limit [Number|String] - number of records to be fetched when fetching chunks (optional)
60
+ # :timestamp [String] - temporal checkpoint(i.e. date, records from which are interested)
61
+ # (optional, format: yyyy-MM-dd’T’HH:mm:ssZ, GMT +2)
62
+ def permitted_params
63
+ %i(country location offset limit timestamp)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,32 @@
1
+ # Module representing parsing utility methods
2
+ module Gdsapi
3
+ module V2
4
+ module Methods
5
+ module ParseUtils
6
+ ## Helping routine for matching given attributes with dictionary
7
+ ## @param set [Array[Hash] | nil] - set of {'id', 'value'}-ish attributes for country
8
+ ## @param dictionary [Array[Hash]] - dictionary of {'id', 'name'}-ish attributes
9
+ ## @return [Array[Hash]|nil] - {id:, name:, value:}-ish attributes hash if set is present, nil otherwise
10
+ def match_attributes(set, dictionary)
11
+ return unless set
12
+
13
+ grouped_set = set.group_by { |attribute| attribute['id'] }
14
+ grouped_set.map do |id, set_attributes|
15
+ dictionary_attribute = dictionary.find {|dict_attr| dict_attr['id'] == id }
16
+ values = set_attributes.map { |set_attribute| set_attribute['value'] }
17
+ if dictionary_attribute
18
+ {
19
+ id: dictionary_attribute['id'],
20
+ name: dictionary_attribute['name'],
21
+ values: values,
22
+ }
23
+ else
24
+ raise MalformedGdsResponse,
25
+ "Invalid response: missing attribute #{id}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ # Requester class
2
+ # Encapsulates supportive data to be used when querying
3
+ # Also responsible for performing HTTP requests
4
+ module Gdsapi
5
+ module V2
6
+ class Requester
7
+ attr_reader :login
8
+ attr_reader :password
9
+ attr_reader :driver
10
+ attr_reader :language
11
+
12
+ # @param [Faraday::Connection] driver - driver instance, i.e. object that responds to
13
+ # #get(url, params, headers),
14
+ # #post(url, body, params),
15
+ # #basic_auth(login, password),
16
+ # and querying and modifying #headers in Hash-style
17
+ # It is `strongly recommended` to use Faraday
18
+ # @param login: & password: - HTTP basic auth factors
19
+ # @param language: - language slug for which request will be performed
20
+ def initialize(driver, login: '', password: '', language: 'ru')
21
+ @driver = driver
22
+ @login = login || ''
23
+ @password = password || ''
24
+ @language = language || 'ru'
25
+ set_language
26
+ set_basic_auth
27
+ end
28
+
29
+ def language=(value)
30
+ @language = value
31
+ set_language
32
+ end
33
+
34
+ def get(url, params: {}, headers: {})
35
+ driver.get url, params, headers
36
+ end
37
+
38
+ def post(url, body: {}, headers: {})
39
+ driver.post url, body, headers
40
+ end
41
+
42
+ private
43
+
44
+ def set_language
45
+ driver.headers['Accept-Language'] = language
46
+ end
47
+
48
+ def set_basic_auth
49
+ driver.basic_auth login, password
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ require 'dry-struct'
2
+ #TODO: Here provide package documentation
3
+ ## This package is used to declare struct types expected from GDS
4
+ ## dry-struct is used in order to provide type definitions and flexible coercions mechanism
5
+ module Gdsapi
6
+ module V2
7
+ module Structs
8
+ include Dry::Types.module
9
+
10
+ Attributes = Structs::Array.member(Structs::Hash.symbolized(id: Structs::Coercible::Int,
11
+ name: Structs::Coercible::String,
12
+ values: Structs::Array.member(Structs::Coercible::String)))
13
+
14
+ Type = Structs::Hash.symbolized(id: Structs::Coercible::Int, name: Structs::Coercible::String)
15
+
16
+ Parent = Structs::Hash.symbolized(id: Structs::Coercible::Int)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../structs'
2
+
3
+ ## Country struct
4
+ ## Used when syncing geodata with GDS
5
+ ## See [Gdsapi::V2::Methods::GetCountries] for details
6
+ ## Also see http://demo.gillbus.com/v2/doc.html#получение-географии-страны for schema definition
7
+ module Gdsapi
8
+ module V2
9
+ module Structs
10
+ class Country < Dry::Struct
11
+ attribute :id, Structs::Coercible::Int
12
+ attribute :name, Structs::Coercible::String
13
+ attribute :type, Type.optional
14
+ attribute :attributes, Attributes.optional
15
+
16
+ def attribute_by_id(id)
17
+ if attributes && id
18
+ attributes.find {|attribute| attribute.id == id}
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ ## Country struct
2
+ ## Used when syncing geodata with GDS
3
+ ## See [[Gdsapi::V2::Methods::GetLocations]] for details
4
+ ## Also see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-населенных-пунктов-get] for schema definition
5
+ module Gdsapi
6
+ module V2
7
+ module Structs
8
+ class Location < Dry::Struct
9
+ attribute :id, Structs::Coercible::Int
10
+ attribute :name, Structs::Coercible::String
11
+ attribute :latitude, Structs::Coercible::Float
12
+ attribute :longitude, Structs::Coercible::Float
13
+ attribute :parent, Parent.optional
14
+ attribute :type, Type.optional
15
+ attribute :sub_type, Type.optional
16
+ attribute :attributes, Attributes.optional
17
+
18
+ def attribute_by_id(id)
19
+ if attributes && id
20
+ attributes.find {|attribute| attribute.id == id}
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ ## Point struct
2
+ ## Used when syncing geodata with GDS
3
+ ## See [[Gdsapi::V2::Methods::GetPoints]] for details
4
+ ## Also see [http://demo.gillbus.com/v2/doc.html#получение-географии-получение-списка-остановок-get] for schema definition
5
+ module Gdsapi
6
+ module V2
7
+ module Structs
8
+ class Point < Dry::Struct
9
+ attribute :id, Structs::Coercible::Int
10
+ attribute :name, Structs::Coercible::String
11
+ attribute :latitude, Structs::Coercible::Float
12
+ attribute :longitude, Structs::Coercible::Float
13
+ attribute :parent, Parent.optional
14
+ attribute :address, Structs::Coercible::String
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Gdsapi
2
+ module V2
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gdsapi-v2-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Yurchenkov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-struct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.49'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.49'
125
+ - !ruby/object:Gem::Dependency
126
+ name: faraday
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
139
+ description:
140
+ email:
141
+ - mikesehse@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - Gemfile
149
+ - README.md
150
+ - Rakefile
151
+ - bin/console
152
+ - bin/setup
153
+ - credentials.json.sample
154
+ - gdsapi-v2-ruby.gemspec
155
+ - lib/gdsapi.rb
156
+ - lib/gdsapi/client.rb
157
+ - lib/gdsapi/methods.rb
158
+ - lib/gdsapi/methods/base_method.rb
159
+ - lib/gdsapi/methods/get_available_trips.rb
160
+ - lib/gdsapi/methods/get_countries.rb
161
+ - lib/gdsapi/methods/get_locations.rb
162
+ - lib/gdsapi/methods/get_points.rb
163
+ - lib/gdsapi/methods/utils/parse_utils.rb
164
+ - lib/gdsapi/requester.rb
165
+ - lib/gdsapi/structs.rb
166
+ - lib/gdsapi/structs/country.rb
167
+ - lib/gdsapi/structs/location.rb
168
+ - lib/gdsapi/structs/point.rb
169
+ - lib/gdsapi/version.rb
170
+ homepage: https://busfor.ru
171
+ licenses:
172
+ - MIT
173
+ metadata: {}
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubyforge_project:
190
+ rubygems_version: 2.5.2
191
+ signing_key:
192
+ specification_version: 4
193
+ summary: Driver for API.GDS.BUSFOR v2
194
+ test_files: []