yandex_client 0.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YandexClient
4
+ module ErrorHandler
5
+ private
6
+
7
+ def error_for_response(response)
8
+ msg = response.body.empty? ? response.status.to_s : response.body.to_s.force_encoding('UTF-8')
9
+ err =
10
+ case response.code
11
+ when 404
12
+ NotFoundError.new(msg, http_code: 404)
13
+ else
14
+ ApiRequestError.new(msg, http_code: response.code)
15
+ end
16
+
17
+ raise err
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YandexClient
4
+ # https://tech.yandex.ru/passport/doc/dg/reference/request-docpage/
5
+ #
6
+ # Example:
7
+ # YandexClient::Passport::Client[Token.first.access_token].info
8
+ class Passport
9
+ include Configurable
10
+ include ErrorHandler
11
+
12
+ ACTION_URL = 'https://login.yandex.ru/info'
13
+
14
+ attr_reader :token
15
+
16
+ class << self
17
+ def with_token(token)
18
+ new(token)
19
+ end
20
+
21
+ alias [] with_token
22
+ end
23
+
24
+ def initialize(token)
25
+ @token = token
26
+ end
27
+
28
+ def info
29
+ process_response \
30
+ with_config.get(ACTION_URL, headers: auth_headers)
31
+ end
32
+
33
+ private
34
+
35
+ def auth_headers
36
+ {Authorization: "Bearer #{@token}"}
37
+ end
38
+
39
+ def process_response(response)
40
+ return JSON.parse(response.body, symbolize_names: true) if response.status.success?
41
+
42
+ error_for_response(response)
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YandexClient
4
- VERSION = '0.1.2'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/yandex_client.rb CHANGED
@@ -2,31 +2,50 @@
2
2
 
3
3
  require 'yandex_client/version'
4
4
 
5
- module YandexClient
6
- Config = Struct.new(:api_key, :api_secret, :logger, :read_timeout)
5
+ require 'yandex_client/error_handler'
6
+ require 'yandex_client/configurable'
7
7
 
8
- def self.config
9
- @config ||= Config.new(nil, nil, nil, 10)
10
- end
8
+ require 'yandex_client/dav'
9
+ require 'yandex_client/disk'
10
+ require 'yandex_client/auth'
11
+ require 'yandex_client/passport'
11
12
 
12
- def self.configure
13
- yield config
13
+ module YandexClient
14
+ Config = Struct.new \
15
+ :api_key,
16
+ :api_secret,
17
+ :logger,
18
+ :read_timeout,
19
+ :write_timeout,
20
+ :connect_timeout
21
+
22
+ class ApiRequestError < StandardError
23
+ attr_reader :http_code
24
+
25
+ def initialize(msg = nil, http_code: nil)
26
+ if http_code
27
+ super "#{msg}, (http code #{http_code})"
28
+ else
29
+ super msg
30
+ end
31
+
32
+ @http_code = http_code
33
+ end
14
34
  end
15
35
 
16
- autoload :Client, 'yandex_client/client'
17
- autoload :ApiRequestError, 'yandex_client/api_request_error'
18
- autoload :NotFoundError, 'yandex_client/not_found_error'
36
+ NotFoundError = Class.new(ApiRequestError)
19
37
 
20
- module Auth
21
- autoload :Client, 'yandex_client/auth/client'
22
- end
38
+ class << self
39
+ def config
40
+ @config ||= Config.new(nil, nil, nil, 120, 600, 5)
41
+ end
23
42
 
24
- module Passport
25
- autoload :Client, 'yandex_client/passport/client'
26
- end
43
+ def configure
44
+ yield config
45
+ end
27
46
 
28
- module Dav
29
- autoload :Client, 'yandex_client/dav/client'
30
- autoload :PropfindParser, 'yandex_client/dav/propfind_parser'
47
+ def auth
48
+ @auth ||= Auth.new
49
+ end
31
50
  end
32
51
  end
@@ -23,12 +23,13 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ['lib']
25
25
 
26
- spec.add_runtime_dependency 'oj'
26
+ spec.add_runtime_dependency 'http'
27
27
  spec.add_runtime_dependency 'ox'
28
28
 
29
29
  spec.add_development_dependency 'bundler'
30
30
  spec.add_development_dependency 'rake', '>= 10'
31
31
  spec.add_development_dependency 'rspec', '>= 3.0'
32
+ spec.add_development_dependency 'simplecov', '>= 0.9'
32
33
  spec.add_development_dependency 'vcr'
33
34
  spec.add_development_dependency 'webmock'
34
35
 
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yandex_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxim Tretyakov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-23 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: oj
14
+ name: http
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: vcr
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -115,10 +129,11 @@ executables: []
115
129
  extensions: []
116
130
  extra_rdoc_files: []
117
131
  files:
132
+ - ".github/workflows/main.yml"
118
133
  - ".gitignore"
134
+ - ".pryrc"
119
135
  - ".rspec"
120
136
  - ".rubocop.yml"
121
- - ".travis.yml"
122
137
  - Gemfile
123
138
  - LICENSE.txt
124
139
  - README.md
@@ -126,17 +141,17 @@ files:
126
141
  - bin/console
127
142
  - bin/setup
128
143
  - dip.yml
129
- - docker/Dockerfile.2.6
130
- - docker/docker-compose.development.yml
144
+ - docker/Dockerfile.dip
131
145
  - docker/docker-compose.yml
146
+ - docker/prepare_env.sh
132
147
  - lib/yandex_client.rb
133
- - lib/yandex_client/api_request_error.rb
134
- - lib/yandex_client/auth/client.rb
135
- - lib/yandex_client/client.rb
136
- - lib/yandex_client/dav/client.rb
137
- - lib/yandex_client/dav/propfind_parser.rb
138
- - lib/yandex_client/not_found_error.rb
139
- - lib/yandex_client/passport/client.rb
148
+ - lib/yandex_client/auth.rb
149
+ - lib/yandex_client/configurable.rb
150
+ - lib/yandex_client/dav.rb
151
+ - lib/yandex_client/dav/prop_find_response.rb
152
+ - lib/yandex_client/disk.rb
153
+ - lib/yandex_client/error_handler.rb
154
+ - lib/yandex_client/passport.rb
140
155
  - lib/yandex_client/version.rb
141
156
  - yandex_client.gemspec
142
157
  homepage: https://github.com/yamax2/yandex_client
@@ -158,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
173
  - !ruby/object:Gem::Version
159
174
  version: '0'
160
175
  requirements: []
161
- rubygems_version: 3.0.6
176
+ rubygems_version: 3.2.5
162
177
  signing_key:
163
178
  specification_version: 4
164
179
  summary: Yandex Client
data/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.6.5
5
- before_install: gem install bundler
6
- before_script:
7
- - bundle install
8
- script: bundle exec rspec
9
- notifications:
10
- email:
11
- on_success: never
12
- on_failure: always
@@ -1,13 +0,0 @@
1
- FROM ruby:2.6.5-alpine
2
-
3
- RUN apk add --update --no-cache tzdata openssh-client less git build-base
4
- RUN cp /usr/share/zoneinfo/Asia/Yekaterinburg /etc/localtime && echo 'Asia/Yekaterinburg' > /etc/timezone
5
-
6
- RUN ssh-keyscan -H github.com >> /etc/ssh/ssh_known_hosts
7
-
8
- ENV BUNDLE_APP_CONFIG /app/.bundle
9
-
10
- RUN echo 'gem: --no-rdoc --no-ri --no-document' > /root/.gemrc
11
- RUN gem install 'bundler:2.0.2' rubocop
12
-
13
- WORKDIR /app
@@ -1,18 +0,0 @@
1
- version: '2'
2
-
3
- services:
4
- app:
5
- volumes:
6
- - ..:/app
7
- - ../:/localgems
8
- - ssh-data:/ssh:ro
9
- - bundler-data:/bundle
10
-
11
- volumes:
12
- bundler-data:
13
- external:
14
- name: bundler_data
15
-
16
- ssh-data:
17
- external:
18
- name: ssh_data
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YandexClient
4
- class ApiRequestError < StandardError
5
- attr_reader :error, :error_description, :code
6
-
7
- def initialize(error:, error_description:, code:)
8
- @error = error
9
- @error_description = error_description
10
- @code = code
11
-
12
- super [error, error_description, "http code #{@code}"].compact.join(', ')
13
- end
14
- end
15
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
-
5
- module YandexClient
6
- module Auth
7
- # https://tech.yandex.ru/oauth/doc/dg/reference/refresh-client-docpage/
8
- # https://tech.yandex.ru/oauth/doc/dg/reference/auto-code-client-docpage/#auto-code-client__get-token
9
- #
10
- # https://oauth.yandex.ru/authorize?response_type=code&client_id=99bcbd17ad7f411694710592d978a4a2&force_confirm=false
11
- #
12
- # Example:
13
- # token = Token.first
14
- #
15
- # client = YandexClient::Auth::Client.new
16
- # client.create_token(code: '9388894')
17
- # client.refresh_token(refresh_token: token.refresh_token)
18
- class Client < ::YandexClient::Client
19
- AUTH_ACTION_URL = 'https://oauth.yandex.ru/token'
20
-
21
- ACTIONS = {
22
- create_token: 'authorization_code',
23
- refresh_token: 'refresh_token'
24
- }.freeze
25
-
26
- private
27
-
28
- def http(_request_uri)
29
- @http ||= super
30
- end
31
-
32
- def http_method_for_action
33
- METHOD_POST
34
- end
35
-
36
- def request_body(params)
37
- body_hash = {
38
- grant_type: ACTIONS.fetch(@action),
39
- client_id: ::YandexClient.config.api_key,
40
- client_secret: ::YandexClient.config.api_secret
41
- }.merge!(params)
42
-
43
- URI.encode_www_form(body_hash)
44
- end
45
-
46
- def request_headers(_params)
47
- {
48
- 'Content-type' => 'application/x-www-form-urlencoded'
49
- }
50
- end
51
-
52
- def request_uri(_params)
53
- @request_uri ||= URI.parse(AUTH_ACTION_URL)
54
- end
55
- end
56
- end
57
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'oj'
4
- require 'net/http'
5
-
6
- module YandexClient
7
- # Base api client
8
- class Client
9
- METHOD_GET = 'Get'
10
- METHOD_POST = 'Post'
11
-
12
- ERRORS_BY_CODES = {
13
- 404 => NotFoundError
14
- }.freeze
15
-
16
- def method_missing(method_name, *args, &_)
17
- return super unless self.class::ACTIONS.include?(method_name)
18
-
19
- @action = method_name
20
- result = nil
21
- response = make_request(args.last.is_a?(Hash) ? args.last : {})
22
-
23
- result = parse_response_body(response) unless response.body.nil? || response.body.empty?
24
- process_errors(response, result) unless response.is_a?(Net::HTTPSuccess)
25
-
26
- result
27
- end
28
-
29
- def respond_to_missing?(method_name, include_private = false)
30
- self.class::ACTIONS.include?(method_name) || super
31
- end
32
-
33
- private
34
-
35
- def http(request_uri)
36
- Net::HTTP.new(request_uri.host, request_uri.port).tap do |http|
37
- http.use_ssl = true
38
- http.read_timeout = YandexClient.config.read_timeout
39
- http.continue_timeout = YandexClient.config.read_timeout
40
- end
41
- end
42
-
43
- def http_method_for_action
44
- METHOD_GET
45
- end
46
-
47
- def log_api_request(request_uri, request, response)
48
- return if (logger = YandexClient.config.logger).nil?
49
-
50
- logger.info "#{request.method} #{request_uri} #{response.code} #{response.message}"
51
- logger.info "request headers: #{Oj.dump(request.to_hash)}"
52
- logger.info "response headers: #{Oj.dump(response.to_hash)}"
53
- end
54
-
55
- def make_request(params)
56
- request_uri = request_uri(params)
57
- @body = request_body(params)
58
-
59
- request = Object.const_get("Net::HTTP::#{http_method_for_action}").new(
60
- request_uri.request_uri,
61
- request_headers(params)
62
- )
63
-
64
- request.body = @body
65
- response = http(request_uri).start { |http| http.request(request) }
66
-
67
- log_api_request(request_uri, request, response)
68
-
69
- response
70
- end
71
-
72
- def parse_response_body(response)
73
- Oj.load(response.body, symbol_keys: true)
74
- end
75
-
76
- def process_errors(response, result)
77
- klass = ERRORS_BY_CODES.fetch(response.code.to_i, ApiRequestError)
78
-
79
- raise klass.new(
80
- error: result.is_a?(Hash) ? result&.fetch(:error) : result,
81
- error_description: result.is_a?(Hash) ? result&.fetch(:error_description) : nil,
82
- code: response.code.to_i
83
- )
84
- end
85
-
86
- def request_body(_params)
87
- end
88
-
89
- def request_headers(_params)
90
- {}
91
- end
92
-
93
- def request_uri(_params)
94
- raise 'not implemented'
95
- end
96
- end
97
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'digest'
4
-
5
- module YandexClient
6
- module Dav
7
- # https://tech.yandex.ru/disk/doc/dg/reference/put-docpage/
8
- #
9
- # cli = YandexClient::Dav::Client.new(access_token: access_token)
10
- #
11
- # cli.put(file: '1.txt', name: '/a/b/c/1.txt')
12
- # cli.delete(name: '/a/b/c/1.txt')
13
- # cli.mkcol(name: '/a/b/c')
14
- #
15
- # cli.propfind(name: '/a/dip.yml', depth: 0)
16
- # cli.propfind(name: '/a', depth: 1)
17
- # cli.propfind(name: '/', quota: true)
18
- class Client < ::YandexClient::Client
19
- ACTION_URL = 'https://webdav.yandex.ru'
20
- PROPFIND_QUERY = '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"></propfind>'
21
- PROPFIND_QUOTA_QUERY = <<~XML
22
- <D:propfind xmlns:D="DAV:">
23
- <D:prop>
24
- <D:quota-available-bytes/>
25
- <D:quota-used-bytes/>
26
- </D:prop>
27
- </D:propfind>
28
- XML
29
-
30
- # actions with header processors
31
- ACTIONS = {
32
- mkcol: nil,
33
- delete: nil,
34
-
35
- put: lambda do |params|
36
- filename = params.fetch(:file)
37
-
38
- etag = params[:etag] || Digest::MD5.file(filename)
39
- sha256 = params[:sha256] || Digest::SHA256.file(filename)
40
- size = params[:size] || File.size(filename)
41
-
42
- {
43
- Etag: etag.to_s,
44
- Sha256: sha256.to_s,
45
- Expect: '100-continue',
46
- 'Content-Length' => size.to_s
47
- }
48
- end,
49
-
50
- propfind: lambda do |params|
51
- depth = 0
52
- depth = params.fetch(:depth, 0) unless params.key?(:quota)
53
-
54
- headers = {
55
- Depth: depth.to_s,
56
- 'Content-Type' => 'application/x-www-form-urlencoded'
57
- }
58
-
59
- headers['Content-Length'] = @body.length unless @body.nil? || @body.empty?
60
- headers
61
- end
62
- }.freeze
63
-
64
- BODY_PROCESSORS = {
65
- put: ->(params) { File.read(params.fetch(:file)) },
66
- propfind: lambda do |params|
67
- if params.fetch(:quota, false)
68
- PROPFIND_QUOTA_QUERY
69
- elsif params.fetch(:depth, 0).zero?
70
- PROPFIND_QUERY
71
- end
72
- end
73
- }.freeze
74
-
75
- RESPONSE_PARSERS = {
76
- propfind: PropfindParser
77
- }.freeze
78
-
79
- def initialize(access_token:)
80
- @access_token = access_token
81
- end
82
-
83
- private
84
-
85
- def http_method_for_action
86
- @action.to_s.capitalize
87
- end
88
-
89
- def parse_response_body(response)
90
- if response.is_a?(Net::HTTPSuccess) && !(parser = RESPONSE_PARSERS[@action]).nil?
91
- parser.call(response.body)
92
- else
93
- response.body
94
- end
95
- end
96
-
97
- def request_body(params)
98
- return if (proc = BODY_PROCESSORS[@action]).nil?
99
-
100
- proc.call(params)
101
- end
102
-
103
- def request_headers(params)
104
- proc = ACTIONS.fetch(@action)
105
-
106
- headers = {Authorization: "OAuth #{@access_token}"}
107
- headers.merge!(proc.call(params)) unless proc.nil?
108
-
109
- headers
110
- end
111
-
112
- def request_uri(params)
113
- URI.parse("#{ACTION_URL}#{params.fetch(:name)}")
114
- end
115
- end
116
- end
117
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ox'
4
- require 'cgi'
5
-
6
- module YandexClient
7
- module Dav
8
- class PropfindParser
9
- SUCCESS_STATUS = 'HTTP/1.1 200 OK'
10
-
11
- PROCESSORS = {
12
- getcontentlength: ->(value) { value.to_i },
13
- resourcetype: ->(value) { value.is_a?(Hash) ? :folder : :file }
14
- }.freeze
15
-
16
- class ParseError < StandardError
17
- end
18
-
19
- def initialize(xml)
20
- # FIXME: dup?
21
- @document = Ox.load(xml.dup.force_encoding('UTF-8'), mode: :hash)
22
- end
23
-
24
- def call
25
- @document[:'d:multistatus'].each_with_object({}) do |node, memo|
26
- next if (response = node[:'d:response']).nil?
27
-
28
- name = CGI.unescape(response.fetch(:'d:href'))
29
- memo[name] = parse_node_props(response.fetch(:'d:propstat'))
30
- end
31
- end
32
-
33
- def self.call(xml)
34
- new(xml).call
35
- end
36
-
37
- private
38
-
39
- def parse_node_props(node)
40
- raise ParseError unless node.fetch(:'d:status') == SUCCESS_STATUS
41
-
42
- node.fetch(:'d:prop').each_with_object({}) do |(key, value), memo|
43
- prop_key = key.to_s.gsub(/^d:/, '').to_sym
44
- processor = PROCESSORS[prop_key]
45
-
46
- memo[prop_key] = processor.nil? ? value : processor.call(value)
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YandexClient
4
- class NotFoundError < ApiRequestError
5
- end
6
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YandexClient
4
- module Passport
5
- # https://tech.yandex.ru/passport/doc/dg/reference/request-docpage/
6
- #
7
- # Example:
8
- # client = YandexClient::Passport::Client.new(access_token: Token.first.access_token)
9
- # client.info
10
- class Client < ::YandexClient::Client
11
- ACTION_URL = 'https://login.yandex.ru/info'
12
- ACTIONS = %i[info].freeze
13
-
14
- # action - info
15
- def initialize(access_token:)
16
- @access_token = access_token
17
- end
18
-
19
- private
20
-
21
- def http(_request_uri)
22
- @http ||= super
23
- end
24
-
25
- def request_uri(_params)
26
- @request_uri ||= URI.parse(ACTION_URL)
27
- end
28
-
29
- def request_headers(_params)
30
- {
31
- Authorization: "Bearer #{@access_token}"
32
- }
33
- end
34
- end
35
- end
36
- end