cobot_client 5.0.0 → 6.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de6d346613c7908a049ea953b3c5eb0eef38ae647246ad9d1bb41d863f7cc104
4
- data.tar.gz: aaa52ccc3005e510d3a63ce9cf736a60ed21ba889c42c7ecaee1e3854a32bb3a
3
+ metadata.gz: 3d961f35435c958c2cf33b88170ce9f5c327f3ee57c332eb534b6b60098a86f1
4
+ data.tar.gz: 33c485cd858e729d508bd331897872f8d24345b75d1e4f192089e7bcfafc2793
5
5
  SHA512:
6
- metadata.gz: 14c6d70b7fef964d12e23f28745bcf33dd4932c6381d2293475e77783f78338357430cc7e0ca98d70f726f826b941f9e4193b107900677f3bca444f24150321c
7
- data.tar.gz: 611a43d02024876126f81d171ec86cc9426ba858767eb2385e435774bab2bbb4fdb5f8328dbc8bd4dace0451054d3f3bc48ad241188b66b31cf2ec584fb74a44
6
+ metadata.gz: 101809ca57f22278713eeedd3f3f3f32c21552b2ed6e15189cb866f1d37d2d104c493acda5348e90dd187d3906e89c1290e35ec549f219caabd86d0adf2a9a5b
7
+ data.tar.gz: 27a001426e07ab13b48724219ae62b58e72a706b9547f4863b97f282c72568dff88657fbd0e380d3c2863e477524291cd8d851cb809c04428166e82199b526bd
@@ -0,0 +1,23 @@
1
+ name: Push gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ jobs:
8
+ release:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: write
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ with:
16
+ persist-credentials: false
17
+ - name: Set up Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ bundler-cache: true
21
+ ruby-version: ruby
22
+ - name: Release gem
23
+ uses: rubygems/release-gem@v1
@@ -0,0 +1,26 @@
1
+ name: Ruby CI
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ pull_request:
7
+ branches:
8
+ - main
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: ["4.0", "3.4", "3.3"]
16
+ steps:
17
+ - uses: actions/checkout@v5
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ bundler-cache: true
23
+ - name: Install RBS type declaration collection
24
+ run: bundle exec rbs collection install
25
+ - name: Run tests
26
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -16,3 +16,6 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .rvmrc
19
+
20
+ /.gem_rbs_collection/
21
+ /rbs_collection.lock.yaml
data/.rubocop.yml ADDED
@@ -0,0 +1,31 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 3.3
8
+
9
+ Layout/SpaceInsideHashLiteralBraces:
10
+ EnforcedStyle: no_space
11
+
12
+ RSpec/AnyInstance:
13
+ Enabled: false
14
+
15
+ RSpec/ExampleLength:
16
+ Enabled: false
17
+
18
+ RSpec/MessageSpies:
19
+ Enabled: false
20
+
21
+ RSpec/MultipleExpectations:
22
+ Enabled: false
23
+
24
+ RSpec/StubbedMock:
25
+ Enabled: false
26
+
27
+ RSpec/VerifiedDoubles:
28
+ Enabled: false
29
+
30
+ Style/Documentation:
31
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 6.0.0
2
+
3
+ - Drop support for EOL Rubies (< 3.3)
4
+ - Add support for Ruby v4
5
+ - Add RBS type declarations
6
+ - Replace discontinued `Virtus` with `ActiveModel::Attributes`
7
+ - Replace abandoned `rest-client` with `net/http`
8
+
1
9
  # 5.0.0
2
10
 
3
11
  Bump oauth2 dependency to ~>2.0.
@@ -8,7 +16,7 @@ Bump oauth2 dependency to ~>2.0.
8
16
 
9
17
  # 3.1.0
10
18
 
11
- - adds user_edtiable attribute to navigation links
19
+ - adds user_editable attribute to navigation links
12
20
 
13
21
  # 3.0.0
14
22
 
data/Gemfile CHANGED
@@ -1,4 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in cobot_client.gemspec
4
6
  gemspec
7
+
8
+ gem 'rake', '~> 13.4'
9
+ gem 'rbs'
10
+ gem 'rspec', '~> 3.0'
11
+ gem 'rubocop'
12
+ gem 'rubocop-rake'
13
+ gem 'rubocop-rspec'
14
+ gem 'webmock', require: false
data/Rakefile CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/gem_tasks'
3
5
  require 'rspec/core/rake_task'
6
+ require 'rubocop/rake_task'
4
7
 
5
8
  RSpec::Core::RakeTask.new(:spec)
9
+ RuboCop::RakeTask.new(:rubocop)
6
10
 
7
- task default: :spec
11
+ task default: %i[spec rubocop]
data/cobot_client.gemspec CHANGED
@@ -12,15 +12,15 @@ Gem::Specification.new do |gem|
12
12
 
13
13
  gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
14
14
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
15
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
15
  gem.name = 'cobot_client'
17
16
  gem.require_paths = ['lib']
18
17
  gem.version = CobotClient::VERSION
19
18
 
20
- gem.add_dependency 'virtus', '~>1.0'
21
- gem.add_dependency 'oauth2', '~>2.0'
22
- gem.add_dependency 'rest-client', '~>2.0.1'
19
+ gem.required_ruby_version = ['>=3.3', '<5']
20
+
21
+ gem.add_dependency 'activemodel', '>=5.2'
23
22
  gem.add_dependency 'json', '~>2.0'
24
- gem.add_development_dependency 'rspec', '~>3.0'
25
- gem.add_development_dependency 'rake', '~>12.3.3'
23
+ gem.add_dependency 'oauth2', '~>2.0'
24
+
25
+ gem.metadata['rubygems_mfa_required'] = 'true'
26
26
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rest_client'
4
- require 'json'
5
-
6
3
  module CobotClient
7
4
  class ApiClient
8
5
  include UrlHelper
@@ -10,6 +7,7 @@ module CobotClient
10
7
  class << self
11
8
  attr_accessor :user_agent, :retry_time
12
9
  end
10
+
13
11
  self.retry_time = 1
14
12
 
15
13
  def initialize(access_token)
@@ -17,106 +15,61 @@ module CobotClient
17
15
  end
18
16
 
19
17
  # args: either a full URL or subdomain, path, plus a body as hash
20
- def post(*args)
21
- request :post, *args
18
+ def post(*)
19
+ request(:post, *).parsed_body
22
20
  end
23
21
 
24
22
  # args: either a full URL or subdomain, path, plus a body as hash
25
- def put(*args)
26
- request :put, *args
23
+ def put(*)
24
+ request(:put, *).parsed_body
27
25
  end
28
26
 
29
- def patch(*args)
30
- request :patch, *args
27
+ def patch(*)
28
+ request(:patch, *).parsed_body
31
29
  end
32
30
 
33
31
  # args: either a full URL or subdomain, path, plus an optional params hash
34
- def get(*args)
35
- url, subdomain, path, params = parse_args(*args)
36
- JSON.parse(
37
- rewrap_errors do
38
- RestClient.get(
39
- build_url(url || subdomain, path, params),
40
- headers
41
- ).body
42
- end, symbolize_names: true
43
- )
32
+ def get(*)
33
+ request(:get, *).parsed_body
44
34
  end
45
35
 
46
36
  # args: either a full URL or subdomain, path
47
- def delete(*args)
48
- url, subdomain, path, = parse_args(*args)
49
- rewrap_errors do
50
- RestClient.delete(build_url(url || subdomain, path), headers)
51
- end
37
+ def delete(*)
38
+ request(:delete, *)
52
39
  end
53
40
 
54
41
  private
55
42
 
56
- def request(method, *args)
57
- url, subdomain, path, body = parse_args(*args)
58
- rewrap_errors do
59
- response = RestClient.public_send(method,
60
- build_url(url || subdomain, path),
61
- body.to_json,
62
- headers.merge(content_type_header))
63
- JSON.parse response.body, symbolize_names: true unless response.code == 204
43
+ def request(method, *)
44
+ request = Request.new(method, *)
45
+ request.headers = headers
46
+
47
+ retry_errors do
48
+ request.submit
64
49
  end
65
50
  end
66
51
 
67
- def rewrap_errors(&block)
68
- retry_errors(&block)
69
- rescue RestClient::Exception => e
70
- raise CobotClient::Exceptions::EXCEPTIONS_MAP[e.class], e.response
52
+ def rewrap_errors
53
+ yield.tap do |response|
54
+ raise response.to_error if response.client_error? || response.server_error?
55
+ end
56
+ rescue Net::ProtocolError, SocketError, Timeout::Error => e
57
+ raise ConnectionError, "#{e.class}: #{e.message}"
71
58
  end
72
59
 
73
- def retry_errors
60
+ def retry_errors(&)
74
61
  retries = 0
75
62
  begin
76
- yield
77
- rescue RestClient::BadGateway, SocketError, RestClient::RequestTimeout, CobotClient::InternalServerError => e
78
- if retries < 3
79
- sleep self.class.retry_time
80
- retries += 1
81
- retry
82
- else
83
- raise e
84
- end
85
- end
86
- end
87
-
88
- def parse_args(*args)
89
- params = if args.size == 3 || (args.size == 2 && args[0].match(%r{https?://}))
90
- args.pop
91
- else
92
- {}
93
- end
94
- if args.size == 1
95
- url = args[0]
96
- path = nil
97
- subdomain = nil
98
- else
99
- subdomain = args[0]
100
- path = args[1]
101
- url = nil
102
- end
103
- [url, subdomain, path, params]
104
- end
63
+ rewrap_errors(&)
64
+ rescue ConnectionError, BadGateway, InternalServerError => e
65
+ raise e unless retries < 3
105
66
 
106
- def build_url(subdomain_or_url, path, params = {})
107
- if path
108
- cobot_url(subdomain_or_url, "/api#{path}", params: params)
109
- else
110
- uri = URI.parse(subdomain_or_url)
111
- uri.query = URI.encode_www_form(params) if params&.any?
112
- uri.to_s
67
+ sleep self.class.retry_time
68
+ retries += 1
69
+ retry
113
70
  end
114
71
  end
115
72
 
116
- def content_type_header
117
- { 'Content-Type' => 'application/json' }
118
- end
119
-
120
73
  def headers
121
74
  {
122
75
  'Authorization' => "Bearer #{@access_token}",
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CobotClient
2
4
  class Engine < Rails::Engine
3
5
  isolate_namespace CobotClient
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobotClient
4
+ class Error < StandardError; end
5
+
6
+ class ConnectionError < Error; end
7
+
8
+ class ResponseError < Error
9
+ HTTP_CODE = nil
10
+
11
+ attr_reader :response
12
+
13
+ def self.build(msg = nil, response:)
14
+ RESPONSE_CODE_TO_ERROR_CLASS
15
+ .fetch(response.code, self)
16
+ .new(msg, response: response)
17
+ end
18
+
19
+ def initialize(msg = nil, response: nil)
20
+ @response = response
21
+
22
+ super(
23
+ [
24
+ "HTTP #{http_code}",
25
+ msg
26
+ ].compact.join(' - ')
27
+ )
28
+ end
29
+
30
+ def http_body
31
+ @response&.body
32
+ end
33
+
34
+ def http_code
35
+ @response&.code || self.class.const_get(:HTTP_CODE)
36
+ end
37
+ end
38
+
39
+ RESPONSE_CODE_TO_ERROR_CLASS = ::Net::HTTPResponse::CODE_TO_OBJ.to_h do |code, net_http_class|
40
+ class_name = net_http_class.name.delete_prefix('Net::HTTP')
41
+
42
+ class_object = Class.new(ResponseError)
43
+ class_object.const_get(:HTTP_CODE, code)
44
+
45
+ [code.to_i, const_set(class_name, class_object)]
46
+ end
47
+ end
@@ -1,11 +1,14 @@
1
- require 'virtus'
1
+ # frozen_string_literal: true
2
2
 
3
- class CobotClient::NavigationLink
4
- include Virtus.model
3
+ module CobotClient
4
+ class NavigationLink
5
+ include ActiveModel::Model
6
+ include ActiveModel::Attributes
5
7
 
6
- attribute :section, String
7
- attribute :label, String
8
- attribute :iframe_url, String
9
- attribute :user_url, String
10
- attribute :user_editable, Boolean, default: true
8
+ attribute :section, :string
9
+ attribute :label, :string
10
+ attribute :iframe_url, :string
11
+ attribute :user_url, :string
12
+ attribute :user_editable, :boolean, default: true
13
+ end
11
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'oauth2'
2
4
 
3
5
  module CobotClient
@@ -5,9 +7,9 @@ module CobotClient
5
7
  class NavigationLinkService
6
8
  # api_client - an CobotClient::ApiClient
7
9
  # access_token - an access token string (owner must be admin of the space to be used)
8
- def initialize(api_client, space_sudomain)
10
+ def initialize(api_client, space_subdomain)
9
11
  @api_client = api_client
10
- @subdomain = space_sudomain
12
+ @subdomain = space_subdomain
11
13
  end
12
14
 
13
15
  # Checks if links are already installed and if not installs them.
@@ -16,7 +18,7 @@ module CobotClient
16
18
  #
17
19
  # Returns the links as `[CobotClient::NavigationLink]`
18
20
  def install_links(new_links)
19
- existing_links = get_links
21
+ existing_links = fetch_links
20
22
  missing_links = new_links.reject do |new_link|
21
23
  existing_links.find do |existing_link|
22
24
  existing_link.section == new_link.section && existing_link.iframe_url == new_link.iframe_url
@@ -30,18 +32,21 @@ module CobotClient
30
32
 
31
33
  private
32
34
 
33
- def get_links
35
+ def fetch_links
34
36
  @api_client.get(@subdomain, '/navigation_links').map do |attributes|
35
37
  NavigationLink.new attributes
36
38
  end
37
39
  end
38
40
 
39
41
  def create_link(link)
40
- response = @api_client.post(@subdomain, '/navigation_links',
42
+ response = @api_client.post(
43
+ @subdomain,
44
+ '/navigation_links',
41
45
  section: link.section,
42
46
  label: link.label,
43
47
  iframe_url: link.iframe_url,
44
- user_editable: link.user_editable)
48
+ user_editable: link.user_editable
49
+ )
45
50
 
46
51
  NavigationLink.new response
47
52
  end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobotClient
4
+ class Request
5
+ include UrlHelper
6
+
7
+ CONTENT_TYPE_HEADER = {'Content-Type' => 'application/json'}.freeze
8
+ VERBS = %i[get post put patch delete].freeze
9
+
10
+ attr_reader :body, :headers, :uri, :verb
11
+
12
+ def initialize(verb, *)
13
+ raise ArgumentError, "Unsupported verb: #{verb.inspect}" unless VERBS.include?(verb)
14
+
15
+ @verb = verb
16
+ @headers = CONTENT_TYPE_HEADER
17
+
18
+ url, subdomain, path, params = parse_args(*)
19
+ @uri, @body = case @verb
20
+ when :get, :delete
21
+ [build_uri(url || subdomain, path, **params), nil]
22
+ else
23
+ [build_uri(url || subdomain, path), params.to_json]
24
+ end
25
+ end
26
+
27
+ def headers=(headers)
28
+ raise ArgumentError, "Expected Hash, got: #{headers.inspect}" unless headers.is_a?(Hash)
29
+
30
+ @headers = headers.merge(CONTENT_TYPE_HEADER)
31
+ end
32
+
33
+ def submit
34
+ Response.new(
35
+ http.request(net_http_request)
36
+ )
37
+ end
38
+
39
+ private
40
+
41
+ def build_uri(subdomain_or_url, path, **params)
42
+ if path
43
+ cobot_uri(subdomain_or_url, "/api#{path}", params: params)
44
+ else
45
+ uri = URI.parse(subdomain_or_url)
46
+ uri.query = URI.encode_www_form(params) unless params.empty?
47
+ uri
48
+ end
49
+ end
50
+
51
+ def http
52
+ @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
53
+ http.use_ssl = (uri.scheme == 'https')
54
+ end
55
+ end
56
+
57
+ # Do not memoize this because `headers` can change
58
+ def net_http_request
59
+ request_class.new(uri).tap do |request|
60
+ request.body = body if body
61
+ request.initialize_http_header(headers)
62
+ end
63
+ end
64
+
65
+ def request_class
66
+ case @verb
67
+ when :get then Net::HTTP::Get
68
+ when :post then Net::HTTP::Post
69
+ when :put then Net::HTTP::Put
70
+ when :patch then Net::HTTP::Patch
71
+ when :delete then Net::HTTP::Delete
72
+ end
73
+ end
74
+
75
+ def parse_args(*args)
76
+ params = if args.size == 3 || (args.size == 2 && args[0].match(%r{https?://}))
77
+ args.pop
78
+ else
79
+ {}
80
+ end
81
+
82
+ if args.size == 1
83
+ [args[0], nil, nil, params]
84
+ else
85
+ [nil, args[0], args[1], params]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobotClient
4
+ class Response
5
+ def initialize(net_http_response)
6
+ @net_http_response = net_http_response
7
+ end
8
+
9
+ def body
10
+ @net_http_response.body
11
+ end
12
+
13
+ def headers
14
+ @net_http_response.to_hash
15
+ end
16
+
17
+ def code
18
+ Integer(@net_http_response.code)
19
+ end
20
+
21
+ def parsed_body
22
+ return if !success? || code == 204
23
+
24
+ JSON.parse(@net_http_response.body, symbolize_names: true)
25
+ end
26
+
27
+ def client_error?
28
+ (400..499).cover?(code)
29
+ end
30
+
31
+ def server_error?
32
+ (500..599).cover?(code)
33
+ end
34
+
35
+ def success?
36
+ (200..299).cover?(code)
37
+ end
38
+
39
+ def to_error
40
+ return if success?
41
+
42
+ ResponseError.build(@net_http_response.message, response: self)
43
+ end
44
+ end
45
+ end
@@ -1,28 +1,31 @@
1
- require 'uri'
1
+ # frozen_string_literal: true
2
2
 
3
- module CobotClient::UrlHelper
4
- # set this to override the site for accessing the cobot api
3
+ module CobotClient
4
+ module UrlHelper
5
+ DEFAULT_SITE = 'https://www.cobot.me'
5
6
 
6
- @@site = 'https://www.cobot.me'
7
- def self.site
8
- @@site
9
- end
7
+ # set this to override the site for accessing the cobot api
8
+ def self.site
9
+ @site || DEFAULT_SITE
10
+ end
10
11
 
11
- def self.site=(site)
12
- @@site = site
13
- end
12
+ def self.site=(site)
13
+ @site = site
14
+ end
14
15
 
15
- # generates a url to access the cobot api
16
- # see the spec for usage examples
17
- def cobot_url(subdomain = 'www', *path_options)
18
- path = path_options.first.is_a?(String) ? path_options.first : '/'
19
- options = path_options.find{|p| p.is_a?(Hash)} || {}
16
+ # generates a uri to access the cobot api
17
+ # see the spec for usage examples
18
+ def cobot_uri(subdomain = 'www', path = '/', params: {}, **)
19
+ uri = URI.parse(CobotClient::UrlHelper.site)
20
+ uri.host = uri.host.split('.').tap { |parts| parts[0] = subdomain }.join('.')
21
+ uri.path = path
22
+ uri.query = URI.encode_www_form(params) unless params.empty?
20
23
 
21
- url = URI.parse(CobotClient::UrlHelper.site)
22
- url.host = url.host.split('.').tap{|parts| parts[0] = subdomain}.join('.')
23
- url.path = path
24
- url.query = URI.encode_www_form(options[:params]) if options[:params] && options[:params].any?
24
+ uri
25
+ end
25
26
 
26
- url.to_s
27
+ def cobot_url(subdomain = 'www', path = '/', params: {}, **)
28
+ cobot_uri(subdomain, path, params: params, **).to_s
29
+ end
27
30
  end
28
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CobotClient
2
- VERSION = '5.0.0'
4
+ VERSION = '6.0.0'
3
5
  end
data/lib/cobot_client.rb CHANGED
@@ -1,10 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'uri'
6
+
7
+ require 'active_model'
8
+
1
9
  require 'cobot_client/version'
2
10
  require 'cobot_client/engine' if defined?(Rails)
3
- require 'cobot_client/exceptions'
11
+ require 'cobot_client/errors'
4
12
 
5
13
  module CobotClient
6
14
  autoload :ApiClient, 'cobot_client/api_client'
7
15
  autoload :NavigationLink, 'cobot_client/navigation_link'
8
16
  autoload :NavigationLinkService, 'cobot_client/navigation_link_service'
17
+ autoload :Response, 'cobot_client/response'
18
+ autoload :Request, 'cobot_client/request'
9
19
  autoload :UrlHelper, 'cobot_client/url_helper'
10
20
  end