bundleup-sdk 0.1.0 → 0.2.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.
@@ -1,71 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bundleup
4
- # Main client class for interacting with the BundleUp API
4
+ # Client for core BundleUp API resources.
5
5
  class Client
6
6
  attr_reader :api_key
7
7
 
8
- # Initialize a new BundleUp client
9
- #
10
- # @param api_key [String] Your BundleUp API key
11
- # @raise [ArgumentError] if api_key is nil or empty
12
8
  def initialize(api_key)
13
- raise ArgumentError, 'API key is required to initialize BundleUp SDK.' if api_key.nil? || api_key.to_s.empty?
9
+ raise ArgumentError, 'API key is required to initialize BundleUp SDK.' if api_key.nil? || api_key.empty?
14
10
 
15
11
  @api_key = api_key
16
12
  end
17
13
 
18
- # Access the Connections resource
19
- #
20
- # @return [Bundleup::Connection] Connection resource instance
21
14
  def connections
22
- @connections ||= Bundleup::Connection.new(@api_key)
15
+ @connections ||= Bundleup::Resources::Connection.new(@api_key)
23
16
  end
24
17
 
25
- # Access the Integrations resource
26
- #
27
- # @return [Bundleup::Integration] Integration resource instance
28
18
  def integrations
29
- @integrations ||= Bundleup::Integration.new(@api_key)
19
+ @integrations ||= Bundleup::Resources::Integration.new(@api_key)
30
20
  end
31
21
 
32
- # Access the Webhooks resource
33
- #
34
- # @return [Bundleup::Webhook] Webhook resource instance
35
22
  def webhooks
36
- @webhooks ||= Bundleup::Webhook.new(@api_key)
23
+ @webhooks ||= Bundleup::Resources::Webhook.new(@api_key)
37
24
  end
38
25
 
39
- # Create a Proxy instance for a specific connection
40
- #
41
- # @param connection_id [String] The connection ID
42
- # @return [Bundleup::Proxy] Proxy instance
43
- # @raise [ArgumentError] if connection_id is nil or empty
44
26
  def proxy(connection_id)
45
- if connection_id.nil? || connection_id.to_s.empty?
46
- raise ArgumentError,
47
- 'Connection ID is required to create a Proxy instance.'
27
+ if connection_id.nil? || connection_id.empty?
28
+ raise ArgumentError, 'Connection ID is required to create a Proxy instance.'
48
29
  end
49
30
 
50
31
  Bundleup::Proxy.new(@api_key, connection_id)
51
32
  end
52
33
 
53
- # Create Unify instances for a specific connection
54
- #
55
- # @param connection_id [String] The connection ID
56
- # @return [Hash] Hash with :chat, :git, and :pm Unify instances
57
- # @raise [ArgumentError] if connection_id is nil or empty
58
34
  def unify(connection_id)
59
- if connection_id.nil? || connection_id.to_s.empty?
60
- raise ArgumentError,
61
- 'Connection ID is required to create a Unify instance.'
35
+ if connection_id.nil? || connection_id.empty?
36
+ raise ArgumentError, 'Connection ID is required to create a Unify instance.'
62
37
  end
63
38
 
64
- {
65
- chat: Bundleup::Unify::Chat.new(@api_key, connection_id),
66
- git: Bundleup::Unify::Git.new(@api_key, connection_id),
67
- pm: Bundleup::Unify::PM.new(@api_key, connection_id)
68
- }
39
+ Bundleup::Unify::Client.new(@api_key, connection_id)
69
40
  end
70
41
  end
71
42
  end
@@ -1,115 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bundleup
4
- # Proxy class for making direct calls to integration APIs
4
+ # Client for proxy API requests.
5
5
  class Proxy
6
6
  BASE_URL = 'https://proxy.bundleup.io'
7
7
 
8
8
  attr_reader :api_key, :connection_id
9
9
 
10
- # Initialize a new Proxy instance
11
- #
12
- # @param api_key [String] Your BundleUp API key
13
- # @param connection_id [String] The connection ID
14
10
  def initialize(api_key, connection_id)
15
11
  @api_key = api_key
16
12
  @connection_id = connection_id
17
13
  end
18
14
 
19
- # Make a GET request
20
- #
21
- # @param path [String] The request path
22
- # @param params [Hash] Query parameters
23
- # @return [Hash] Response data
24
- def get(path, params = {})
25
- request(:get, path, params)
26
- end
15
+ def get(path, headers: {})
16
+ raise ArgumentError, 'Path is required for GET request' unless path
27
17
 
28
- # Make a POST request
29
- #
30
- # @param path [String] The request path
31
- # @param body [Hash] Request body
32
- # @return [Hash] Response data
33
- def post(path, body = {})
34
- request(:post, path, body)
18
+ # Merge any additional headers
19
+ request_headers = connection.headers.merge(headers)
20
+ connection.get(path, nil, request_headers)
35
21
  end
36
22
 
37
- # Make a PUT request
38
- #
39
- # @param path [String] The request path
40
- # @param body [Hash] Request body
41
- # @return [Hash] Response data
42
- def put(path, body = {})
43
- request(:put, path, body)
44
- end
23
+ def post(path, body: {}, headers: {})
24
+ raise ArgumentError, 'Path is required for POST request' unless path
45
25
 
46
- # Make a PATCH request
47
- #
48
- # @param path [String] The request path
49
- # @param body [Hash] Request body
50
- # @return [Hash] Response data
51
- def patch(path, body = {})
52
- request(:patch, path, body)
26
+ # Merge any additional headers
27
+ request_headers = connection.headers.merge(headers)
28
+ connection.post(path, body.to_json, request_headers)
53
29
  end
54
30
 
55
- # Make a DELETE request
56
- #
57
- # @param path [String] The request path
58
- # @param params [Hash] Query parameters
59
- # @return [Hash] Response data
60
- def delete(path, params = {})
61
- request(:delete, path, params)
31
+ def put(path, body: {}, headers: {})
32
+ raise ArgumentError, 'Path is required for PUT request' unless path
33
+
34
+ # Merge any additional headers
35
+ request_headers = connection.headers.merge(headers)
36
+ connection.put(path, body.to_json, request_headers)
62
37
  end
63
38
 
64
- private
39
+ def patch(path, body: {}, headers: {})
40
+ raise ArgumentError, 'Path is required for PATCH request' unless path
65
41
 
66
- def request(method, path, body = nil)
67
- response = connection.public_send(method) do |req|
68
- req.url path
69
- req.headers['Content-Type'] = 'application/json'
70
- req.headers['Authorization'] = "Bearer #{api_key}"
71
- req.headers['BU-Connection-Id'] = connection_id
72
-
73
- if body && %i[post patch put].include?(method)
74
- req.body = body.to_json
75
- elsif body && method == :get
76
- req.params.update(body)
77
- end
78
- end
42
+ # Merge any additional headers
43
+ request_headers = connection.headers.merge(headers)
44
+ connection.patch(path, body.to_json, request_headers)
45
+ end
46
+
47
+ def delete(path, headers: {})
48
+ raise ArgumentError, 'Path is required for DELETE request' unless path
79
49
 
80
- handle_response(response)
81
- rescue Faraday::Error => e
82
- raise APIError, "Proxy request failed: #{e.message}"
50
+ # Merge any additional headers
51
+ request_headers = connection.headers.merge(headers)
52
+ connection.delete(path, nil, request_headers)
83
53
  end
84
54
 
55
+ private
56
+
57
+ # Memoize the Faraday connection to reuse it across requests
85
58
  def connection
86
- @connection ||= Faraday.new(url: BASE_URL) do |conn|
87
- conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2
88
- conn.adapter Faraday.default_adapter
89
- end
90
- end
59
+ @connection ||= Faraday.new(url: BASE_URL) do |faraday|
60
+ faraday.headers = {
61
+ 'Authorization' => "Bearer #{@api_key}",
62
+ 'Content-Type' => 'application/json',
63
+ 'BU-Connection-Id' => @connection_id
64
+ }
91
65
 
92
- def handle_response(response)
93
- case response.status
94
- when 200..299
95
- response.body.empty? ? {} : JSON.parse(response.body)
96
- when 401
97
- raise AuthenticationError, 'Invalid API key'
98
- when 400..499
99
- error_message = extract_error_message(response)
100
- raise InvalidRequestError, error_message
101
- when 500..599
102
- raise APIError, 'Server error occurred'
103
- else
104
- raise APIError, "Unexpected response status: #{response.status}"
66
+ faraday.request :json
67
+ faraday.response :json, content_type: /\bjson$/
68
+ faraday.response :raise_error
69
+ faraday.adapter Faraday.default_adapter
105
70
  end
106
71
  end
107
-
108
- def extract_error_message(response)
109
- body = JSON.parse(response.body)
110
- body['error'] || body['message'] || "Request failed with status #{response.status}"
111
- rescue JSON::ParserError
112
- "Request failed with status #{response.status}"
113
- end
114
72
  end
115
73
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Resources
5
+ # Base class for API resources.
6
+ class Base
7
+ BASE_URL = 'https://api.bundleup.io'
8
+ API_VERSION = 'v1'
9
+
10
+ def initialize(api_key)
11
+ @api_key = api_key
12
+ end
13
+
14
+ protected
15
+
16
+ def list(params: {})
17
+ response = connection.get(resource_name, params)
18
+
19
+ raise "Failed to fetch #{resource_name}: #{response.status}: #{response.body}" unless response.success?
20
+
21
+ response.body
22
+ end
23
+
24
+ def create(body: {})
25
+ response = connection.post(resource_name, body.to_json)
26
+
27
+ raise "Failed to create #{resource_name}: #{response.status}" unless response.success?
28
+
29
+ response.body
30
+ end
31
+
32
+ def retrieve(id)
33
+ response = connection.get("#{resource_name}/#{id}")
34
+
35
+ raise "Failed to retrieve #{resource_name}: #{response.status}" unless response.success?
36
+
37
+ response.body
38
+ end
39
+
40
+ def update(id, body: {})
41
+ response = connection.put("#{resource_name}/#{id}", body.to_json)
42
+
43
+ raise "Failed to update #{resource_name}: #{response.status}" unless response.success?
44
+
45
+ response.body
46
+ end
47
+
48
+ def delete(id)
49
+ response = connection.delete("#{resource_name}/#{id}")
50
+
51
+ raise "Failed to delete #{resource_name}: #{response.status}" unless response.success?
52
+
53
+ nil
54
+ end
55
+
56
+ private
57
+
58
+ # Memoize the Faraday connection to reuse it across requests
59
+ def connection
60
+ @connection ||= Faraday.new(url: "#{BASE_URL}/#{API_VERSION}/") do |faraday|
61
+ faraday.headers = {
62
+ 'Authorization' => "Bearer #{@api_key}",
63
+ 'Content-Type' => 'application/json'
64
+ }
65
+
66
+ faraday.request :json
67
+ faraday.response :json, content_type: /\bjson$/
68
+ faraday.response :raise_error
69
+ faraday.adapter Faraday.default_adapter
70
+ end
71
+ end
72
+
73
+ def resource_name
74
+ raise NotImplementedError, 'Subclasses must implement the resource_name method'
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Resources
5
+ # Client for connection API endpoints.
6
+ class Connection < Base
7
+ public :list, :retrieve, :delete
8
+
9
+ private
10
+
11
+ def resource_name
12
+ 'connections'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Resources
5
+ # Client for integration API endpoints.
6
+ class Integration < Base
7
+ public :list, :retrieve
8
+
9
+ private
10
+
11
+ def resource_name
12
+ 'integrations'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Resources
5
+ # Client for webhook API endpoints.
6
+ class Webhook < Base
7
+ public :list, :retrieve, :create, :update, :delete
8
+
9
+ private
10
+
11
+ def resource_name
12
+ 'webhooks'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,17 +2,13 @@
2
2
 
3
3
  module Bundleup
4
4
  module Unify
5
- # Base class for all Unify resources
5
+ # Base class for Unify API clients.
6
6
  class Base
7
7
  BASE_URL = 'https://unify.bundleup.io'
8
8
  API_VERSION = 'v1'
9
9
 
10
10
  attr_reader :api_key, :connection_id
11
11
 
12
- # Initialize a new Unify resource
13
- #
14
- # @param api_key [String] Your BundleUp API key
15
- # @param connection_id [String] The connection ID
16
12
  def initialize(api_key, connection_id)
17
13
  @api_key = api_key
18
14
  @connection_id = connection_id
@@ -20,55 +16,21 @@ module Bundleup
20
16
 
21
17
  private
22
18
 
23
- def request(method, path, params = {}, include_raw: false)
24
- response = connection.public_send(method) do |req|
25
- req.url path
26
- req.headers['Content-Type'] = 'application/json'
27
- req.headers['Authorization'] = "Bearer #{api_key}"
28
- req.headers['BU-Connection-Id'] = connection_id
29
- req.headers['BU-Include-Raw'] = include_raw.to_s
30
-
31
- if params && %i[post patch put].include?(method)
32
- req.body = params.to_json
33
- elsif params && method == :get
34
- req.params.update(params)
35
- end
36
- end
37
-
38
- handle_response(response)
39
- rescue Faraday::Error => e
40
- raise APIError, "Unify request failed: #{e.message}"
41
- end
42
-
19
+ # Memoize the Faraday connection to reuse it across requests
43
20
  def connection
44
- @connection ||= Faraday.new(url: BASE_URL) do |conn|
45
- conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2
46
- conn.adapter Faraday.default_adapter
21
+ @connection ||= Faraday.new(url: "#{BASE_URL}/#{API_VERSION}/") do |faraday|
22
+ faraday.headers = {
23
+ 'Authorization' => "Bearer #{@api_key}",
24
+ 'Content-Type' => 'application/json',
25
+ 'BU-Connection-Id' => @connection_id
26
+ }
27
+
28
+ faraday.request :json
29
+ faraday.response :json, content_type: /\bjson$/
30
+ faraday.response :raise_error
31
+ faraday.adapter Faraday.default_adapter
47
32
  end
48
33
  end
49
-
50
- def handle_response(response)
51
- case response.status
52
- when 200..299
53
- response.body.empty? ? {} : JSON.parse(response.body)
54
- when 401
55
- raise AuthenticationError, 'Invalid API key'
56
- when 400..499
57
- error_message = extract_error_message(response)
58
- raise InvalidRequestError, error_message
59
- when 500..599
60
- raise APIError, 'Server error occurred'
61
- else
62
- raise APIError, "Unexpected response status: #{response.status}"
63
- end
64
- end
65
-
66
- def extract_error_message(response)
67
- body = JSON.parse(response.body)
68
- body['error'] || body['message'] || "Request failed with status #{response.status}"
69
- rescue JSON::ParserError
70
- "Request failed with status #{response.status}"
71
- end
72
34
  end
73
35
  end
74
36
  end
@@ -2,18 +2,17 @@
2
2
 
3
3
  module Bundleup
4
4
  module Unify
5
- # Chat resource for unified chat operations
5
+ # Client for chat-related Unify endpoints.
6
6
  class Chat < Base
7
- # List channels
8
- #
9
- # @param params [Hash] Query parameters
10
- # @option params [Integer] :limit Number of results to return
11
- # @option params [String] :cursor Pagination cursor
12
- # @option params [Boolean] :include_raw Include raw response from the integration
13
- # @return [Hash] Channels data with pagination info
7
+ # Fetches channels from the connected chat provider.
14
8
  def channels(params = {})
15
- include_raw = params.delete(:include_raw) || false
16
- request(:get, "/#{API_VERSION}/chat/channels", params, include_raw: include_raw)
9
+ response = connection.get('chat/channels') do |req|
10
+ req.params = params
11
+ end
12
+
13
+ raise "Failed to fetch chat/channels: #{response.status}" unless response.success?
14
+
15
+ response.body
17
16
  end
18
17
  end
19
18
  end
@@ -2,60 +2,56 @@
2
2
 
3
3
  module Bundleup
4
4
  module Unify
5
- # Git resource for unified git operations
5
+ # Client for Git Unify endpoints.
6
6
  class Git < Base
7
- # List repositories
8
- #
9
- # @param params [Hash] Query parameters
10
- # @option params [Integer] :limit Number of results to return
11
- # @option params [String] :cursor Pagination cursor
12
- # @option params [Boolean] :include_raw Include raw response from the integration
13
- # @return [Hash] Repositories data with pagination info
7
+ # Fetches repositories from the connected Git provider.
14
8
  def repos(params = {})
15
- include_raw = params.delete(:include_raw) || false
16
- request(:get, "/#{API_VERSION}/git/repos", params, include_raw: include_raw)
9
+ response = connection.get('git/repos') do |req|
10
+ req.params = params
11
+ end
12
+
13
+ raise "Failed to fetch git/repos: #{response.status}" unless response.success?
14
+
15
+ response.body
17
16
  end
18
17
 
19
- # List pull requests
20
- #
21
- # @param repo_name [String] Repository name
22
- # @param params [Hash] Query parameters
23
- # @option params [Integer] :limit Number of results to return
24
- # @option params [String] :cursor Pagination cursor
25
- # @option params [Boolean] :include_raw Include raw response from the integration
26
- # @return [Hash] Pull requests data with pagination info
18
+ # Fetches pull requests for a specific repository from the connected Git provider.
27
19
  def pulls(repo_name, params = {})
28
- include_raw = params.delete(:include_raw) || false
29
- params[:repo_name] = repo_name
30
- request(:get, "/#{API_VERSION}/git/pulls", params, include_raw: include_raw)
20
+ encoded_repo_name = URI.encode_www_form_component(repo_name)
21
+
22
+ response = connection.get("git/repos/#{encoded_repo_name}/pulls") do |req|
23
+ req.params = params
24
+ end
25
+
26
+ raise "Failed to fetch git/repos/#{encoded_repo_name}/pulls: #{response.status}" unless response.success?
27
+
28
+ response.body
31
29
  end
32
30
 
33
- # List tags
34
- #
35
- # @param repo_name [String] Repository name
36
- # @param params [Hash] Query parameters
37
- # @option params [Integer] :limit Number of results to return
38
- # @option params [String] :cursor Pagination cursor
39
- # @option params [Boolean] :include_raw Include raw response from the integration
40
- # @return [Hash] Tags data with pagination info
31
+ # Fetches tags for a specific repository from the connected Git provider.
41
32
  def tags(repo_name, params = {})
42
- include_raw = params.delete(:include_raw) || false
43
- params[:repo_name] = repo_name
44
- request(:get, "/#{API_VERSION}/git/tags", params, include_raw: include_raw)
33
+ encoded_repo_name = URI.encode_www_form_component(repo_name)
34
+
35
+ response = connection.get("git/repos/#{encoded_repo_name}/tags") do |req|
36
+ req.params = params
37
+ end
38
+
39
+ raise "Failed to fetch git/repos/#{encoded_repo_name}/tags: #{response.status}" unless response.success?
40
+
41
+ response.body
45
42
  end
46
43
 
47
- # List releases
48
- #
49
- # @param repo_name [String] Repository name
50
- # @param params [Hash] Query parameters
51
- # @option params [Integer] :limit Number of results to return
52
- # @option params [String] :cursor Pagination cursor
53
- # @option params [Boolean] :include_raw Include raw response from the integration
54
- # @return [Hash] Releases data with pagination info
44
+ # Fetches releases for a specific repository from the connected Git provider.
55
45
  def releases(repo_name, params = {})
56
- include_raw = params.delete(:include_raw) || false
57
- params[:repo_name] = repo_name
58
- request(:get, "/#{API_VERSION}/git/releases", params, include_raw: include_raw)
46
+ encoded_repo_name = URI.encode_www_form_component(repo_name)
47
+
48
+ response = connection.get("git/repos/#{encoded_repo_name}/releases") do |req|
49
+ req.params = params
50
+ end
51
+
52
+ raise "Failed to fetch git/repos/#{encoded_repo_name}/releases: #{response.status}" unless response.success?
53
+
54
+ response.body
59
55
  end
60
56
  end
61
57
  end
@@ -2,18 +2,17 @@
2
2
 
3
3
  module Bundleup
4
4
  module Unify
5
- # PM (Project Management) resource for unified project management operations
5
+ # Client for project management Unify endpoints.
6
6
  class PM < Base
7
- # List issues
8
- #
9
- # @param params [Hash] Query parameters
10
- # @option params [Integer] :limit Number of results to return
11
- # @option params [String] :cursor Pagination cursor
12
- # @option params [Boolean] :include_raw Include raw response from the integration
13
- # @return [Hash] Issues data with pagination info
7
+ # Fetches issues from the connected project management tool.
14
8
  def issues(params = {})
15
- include_raw = params.delete(:include_raw) || false
16
- request(:get, "/#{API_VERSION}/pm/issues", params, include_raw: include_raw)
9
+ response = connection.get('pm/issues') do |req|
10
+ req.params = params
11
+ end
12
+
13
+ raise "Failed to fetch pm/issues: #{response.status}" unless response.success?
14
+
15
+ response.body
17
16
  end
18
17
  end
19
18
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Unify
5
+ # Client for Unify API endpoints.
6
+ class Client
7
+ attr_reader :api_key, :connection_id
8
+
9
+ def initialize(api_key, connection_id)
10
+ @pm = Bundleup::Unify::PM.new(api_key, connection_id)
11
+ @chat = Bundleup::Unify::Chat.new(api_key, connection_id)
12
+ @git = Bundleup::Unify::Git.new(api_key, connection_id)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bundleup
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end