attio 0.1.1

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.codecov.yml +52 -0
  3. data/.github/CODEOWNERS +28 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
  6. data/.github/dependabot.yml +54 -0
  7. data/.github/pull_request_template.md +40 -0
  8. data/.github/workflows/ci.yml +112 -0
  9. data/.github/workflows/dependabot-auto-merge.yml +45 -0
  10. data/.github/workflows/pr_checks.yml +145 -0
  11. data/.github/workflows/release.yml +147 -0
  12. data/.gitignore +10 -0
  13. data/.rspec +4 -0
  14. data/.rubocop.yml +133 -0
  15. data/.yardopts +18 -0
  16. data/CHANGELOG.md +34 -0
  17. data/CODE_OF_CONDUCT.md +37 -0
  18. data/CONTRIBUTING.md +280 -0
  19. data/Gemfile +17 -0
  20. data/Gemfile.lock +127 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +292 -0
  23. data/Rakefile +57 -0
  24. data/SECURITY.md +59 -0
  25. data/attio.gemspec +34 -0
  26. data/bin/console +14 -0
  27. data/bin/setup +8 -0
  28. data/danger/Dangerfile +121 -0
  29. data/docs/.nojekyll +0 -0
  30. data/docs/Attio/AuthenticationError.html +152 -0
  31. data/docs/Attio/Client.html +1373 -0
  32. data/docs/Attio/ConnectionPool/TimeoutError.html +148 -0
  33. data/docs/Attio/ConnectionPool.html +944 -0
  34. data/docs/Attio/Error.html +152 -0
  35. data/docs/Attio/HttpClient/ConnectionError.html +152 -0
  36. data/docs/Attio/HttpClient/TimeoutError.html +152 -0
  37. data/docs/Attio/HttpClient.html +1329 -0
  38. data/docs/Attio/Logger.html +747 -0
  39. data/docs/Attio/NotFoundError.html +152 -0
  40. data/docs/Attio/RateLimitError.html +152 -0
  41. data/docs/Attio/RequestLogger.html +780 -0
  42. data/docs/Attio/Resources/Attributes.html +508 -0
  43. data/docs/Attio/Resources/Base.html +624 -0
  44. data/docs/Attio/Resources/Lists.html +1002 -0
  45. data/docs/Attio/Resources/Objects.html +415 -0
  46. data/docs/Attio/Resources/Records.html +1465 -0
  47. data/docs/Attio/Resources/Users.html +415 -0
  48. data/docs/Attio/Resources/Workspaces.html +324 -0
  49. data/docs/Attio/Resources.html +141 -0
  50. data/docs/Attio/RetryHandler.html +1023 -0
  51. data/docs/Attio/ServerError.html +152 -0
  52. data/docs/Attio/ValidationError.html +152 -0
  53. data/docs/Attio.html +397 -0
  54. data/docs/SETUP.md +117 -0
  55. data/docs/_index.html +378 -0
  56. data/docs/class_list.html +54 -0
  57. data/docs/css/common.css +1 -0
  58. data/docs/css/full_list.css +58 -0
  59. data/docs/css/style.css +503 -0
  60. data/docs/example.rb +119 -0
  61. data/docs/file.CHANGELOG.html +124 -0
  62. data/docs/file.README.html +371 -0
  63. data/docs/file_list.html +64 -0
  64. data/docs/frames.html +22 -0
  65. data/docs/index.html +371 -0
  66. data/docs/js/app.js +344 -0
  67. data/docs/js/full_list.js +242 -0
  68. data/docs/js/jquery.js +4 -0
  69. data/docs/method_list.html +750 -0
  70. data/docs/top-level-namespace.html +110 -0
  71. data/lib/attio/client.rb +118 -0
  72. data/lib/attio/connection_pool.rb +69 -0
  73. data/lib/attio/errors.rb +9 -0
  74. data/lib/attio/http_client.rb +100 -0
  75. data/lib/attio/logger.rb +110 -0
  76. data/lib/attio/resources/attributes.rb +26 -0
  77. data/lib/attio/resources/base.rb +55 -0
  78. data/lib/attio/resources/lists.rb +56 -0
  79. data/lib/attio/resources/objects.rb +20 -0
  80. data/lib/attio/resources/records.rb +158 -0
  81. data/lib/attio/resources/users.rb +20 -0
  82. data/lib/attio/resources/workspaces.rb +13 -0
  83. data/lib/attio/retry_handler.rb +70 -0
  84. data/lib/attio/version.rb +3 -0
  85. data/lib/attio.rb +60 -0
  86. data/run_tests.rb +52 -0
  87. data/test_basic.rb +51 -0
  88. data/test_typhoeus.rb +31 -0
  89. metadata +160 -0
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Attio Ruby Client Documentation
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Attio.html" title="Attio (module)">Attio</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Mon Aug 11 11:26:43 2025 by
104
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.37 (ruby-3.4.5).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,118 @@
1
+ module Attio
2
+ # The main client class for interacting with the Attio API.
3
+ #
4
+ # This class provides access to all Attio API resources and handles
5
+ # authentication, connection management, and request routing.
6
+ #
7
+ # @example Basic client creation
8
+ # client = Attio::Client.new(api_key: 'your-api-key')
9
+ #
10
+ # @example Custom timeout
11
+ # client = Attio::Client.new(api_key: 'your-api-key', timeout: 60)
12
+ #
13
+ # @author Ernest Sim
14
+ # @since 1.0.0
15
+ class Client
16
+ # The base URL for the Attio API v2
17
+ API_BASE_URL = "https://api.attio.com/v2".freeze
18
+
19
+ # Default request timeout in seconds
20
+ DEFAULT_TIMEOUT = 30
21
+
22
+ # @return [String] The API key used for authentication
23
+ attr_reader :api_key
24
+
25
+ # @return [Integer] The request timeout in seconds
26
+ attr_reader :timeout
27
+
28
+ # Initialize a new Attio API client.
29
+ #
30
+ # @param api_key [String] Your Attio API key (required)
31
+ # @param timeout [Integer] Request timeout in seconds (default: 30)
32
+ # @raise [ArgumentError] if api_key is nil or empty
33
+ #
34
+ # @example
35
+ # client = Attio::Client.new(api_key: 'sk-...your-key...')
36
+ def initialize(api_key:, timeout: DEFAULT_TIMEOUT)
37
+ raise ArgumentError, "API key is required" if api_key.nil? || api_key.empty?
38
+
39
+ @api_key = api_key
40
+ @timeout = timeout
41
+ end
42
+
43
+ # Returns the HTTP connection instance for making API requests.
44
+ #
45
+ # This method creates and configures the HTTP client with proper
46
+ # authentication headers and settings. The connection is cached
47
+ # for subsequent requests.
48
+ #
49
+ # @return [HttpClient] The configured HTTP client instance
50
+ # @api private
51
+ def connection
52
+ @connection ||= HttpClient.new(
53
+ base_url: API_BASE_URL,
54
+ headers: {
55
+ "Authorization" => "Bearer #{api_key}",
56
+ "Accept" => "application/json",
57
+ "Content-Type" => "application/json",
58
+ "User-Agent" => "Attio Ruby Client/#{VERSION}"
59
+ },
60
+ timeout: timeout
61
+ )
62
+ end
63
+
64
+ # Access to the Records API resource.
65
+ #
66
+ # @return [Resources::Records] Records resource instance
67
+ # @example
68
+ # records = client.records.list(object: 'people')
69
+ def records
70
+ @records ||= Resources::Records.new(self)
71
+ end
72
+
73
+ # Access to the Objects API resource.
74
+ #
75
+ # @return [Resources::Objects] Objects resource instance
76
+ # @example
77
+ # objects = client.objects.list
78
+ def objects
79
+ @objects ||= Resources::Objects.new(self)
80
+ end
81
+
82
+ # Access to the Lists API resource.
83
+ #
84
+ # @return [Resources::Lists] Lists resource instance
85
+ # @example
86
+ # lists = client.lists.list
87
+ def lists
88
+ @lists ||= Resources::Lists.new(self)
89
+ end
90
+
91
+ # Access to the Workspaces API resource.
92
+ #
93
+ # @return [Resources::Workspaces] Workspaces resource instance
94
+ # @example
95
+ # workspaces = client.workspaces.list
96
+ def workspaces
97
+ @workspaces ||= Resources::Workspaces.new(self)
98
+ end
99
+
100
+ # Access to the Attributes API resource.
101
+ #
102
+ # @return [Resources::Attributes] Attributes resource instance
103
+ # @example
104
+ # attributes = client.attributes.list
105
+ def attributes
106
+ @attributes ||= Resources::Attributes.new(self)
107
+ end
108
+
109
+ # Access to the Users API resource.
110
+ #
111
+ # @return [Resources::Users] Users resource instance
112
+ # @example
113
+ # users = client.users.list
114
+ def users
115
+ @users ||= Resources::Users.new(self)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,69 @@
1
+ require 'thread'
2
+
3
+ module Attio
4
+ class ConnectionPool
5
+ DEFAULT_POOL_SIZE = 5
6
+ DEFAULT_TIMEOUT = 5 # seconds to wait for connection
7
+
8
+ attr_reader :size, :timeout
9
+
10
+ def initialize(size: DEFAULT_POOL_SIZE, timeout: DEFAULT_TIMEOUT, &block)
11
+ @size = size
12
+ @timeout = timeout
13
+ @available = Queue.new
14
+ @key = :"#{object_id}_connection"
15
+ @block = block
16
+ @mutex = Mutex.new
17
+
18
+ size.times { @available << create_connection }
19
+ end
20
+
21
+ def with_connection
22
+ connection = checkout
23
+ begin
24
+ yield connection
25
+ ensure
26
+ checkin(connection)
27
+ end
28
+ end
29
+
30
+ def checkout
31
+ deadline = Time.now + timeout
32
+
33
+ loop do
34
+ return @available.pop(true) if @available.size > 0
35
+
36
+ if Time.now >= deadline
37
+ raise TimeoutError, "Couldn't acquire connection within #{timeout} seconds"
38
+ end
39
+
40
+ sleep(0.01)
41
+ end
42
+ rescue ThreadError
43
+ raise TimeoutError, "No connections available"
44
+ end
45
+
46
+ def checkin(connection)
47
+ @mutex.synchronize do
48
+ @available << connection if connection
49
+ end
50
+ end
51
+
52
+ def shutdown
53
+ @mutex.synchronize do
54
+ @available.close
55
+ while connection = @available.pop(true) rescue nil
56
+ connection.close if connection.respond_to?(:close)
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def create_connection
64
+ @block.call
65
+ end
66
+
67
+ class TimeoutError < StandardError; end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ module Attio
2
+ class Error < StandardError; end
3
+
4
+ class AuthenticationError < Error; end
5
+ class NotFoundError < Error; end
6
+ class ValidationError < Error; end
7
+ class RateLimitError < Error; end
8
+ class ServerError < Error; end
9
+ end
@@ -0,0 +1,100 @@
1
+ require 'typhoeus'
2
+ require 'json'
3
+
4
+ module Attio
5
+ class HttpClient
6
+ DEFAULT_TIMEOUT = 30
7
+
8
+ attr_reader :base_url, :headers, :timeout
9
+
10
+ def initialize(base_url:, headers: {}, timeout: DEFAULT_TIMEOUT)
11
+ @base_url = base_url
12
+ @headers = headers
13
+ @timeout = timeout
14
+ end
15
+
16
+ def get(path, params = {})
17
+ execute_request(:get, path, params: params)
18
+ end
19
+
20
+ def post(path, body = {})
21
+ execute_request(:post, path, body: body.to_json)
22
+ end
23
+
24
+ def patch(path, body = {})
25
+ execute_request(:patch, path, body: body.to_json)
26
+ end
27
+
28
+ def put(path, body = {})
29
+ execute_request(:put, path, body: body.to_json)
30
+ end
31
+
32
+ def delete(path)
33
+ execute_request(:delete, path)
34
+ end
35
+
36
+ private
37
+
38
+ def execute_request(method, path, options = {})
39
+ url = "#{base_url}/#{path}"
40
+
41
+ request_options = {
42
+ method: method,
43
+ headers: headers.merge('Content-Type' => 'application/json'),
44
+ timeout: timeout,
45
+ connecttimeout: timeout
46
+ }.merge(options)
47
+
48
+ request = Typhoeus::Request.new(url, request_options)
49
+ response = request.run
50
+
51
+ handle_response(response)
52
+ end
53
+
54
+ def handle_response(response)
55
+ case response.code
56
+ when 0
57
+ # Timeout or connection error
58
+ if response.timed_out?
59
+ raise TimeoutError, "Request timed out"
60
+ else
61
+ raise ConnectionError, "Connection failed: #{response.return_message}"
62
+ end
63
+ when 200..299
64
+ parse_json(response.body)
65
+ when 401
66
+ raise AuthenticationError, parse_error_message(response)
67
+ when 404
68
+ raise NotFoundError, parse_error_message(response)
69
+ when 422
70
+ raise ValidationError, parse_error_message(response)
71
+ when 429
72
+ raise RateLimitError, parse_error_message(response)
73
+ when 500..599
74
+ raise ServerError, parse_error_message(response)
75
+ else
76
+ raise Error, "Request failed with status #{response.code}: #{parse_error_message(response)}"
77
+ end
78
+ end
79
+
80
+ def parse_json(body)
81
+ return {} if body.nil? || body.empty?
82
+ JSON.parse(body)
83
+ rescue JSON::ParserError => e
84
+ raise Error, "Invalid JSON response: #{e.message}"
85
+ end
86
+
87
+ def parse_error_message(response)
88
+ body = parse_json(response.body) rescue response.body
89
+
90
+ if body.is_a?(Hash)
91
+ body["error"] || body["message"] || body.to_s
92
+ else
93
+ body.to_s
94
+ end
95
+ end
96
+
97
+ class TimeoutError < Error; end
98
+ class ConnectionError < Error; end
99
+ end
100
+ end
@@ -0,0 +1,110 @@
1
+ require 'logger'
2
+ require 'json'
3
+
4
+ module Attio
5
+ class Logger < ::Logger
6
+ def initialize(logdev, level: ::Logger::INFO, formatter: nil)
7
+ super(logdev)
8
+ self.level = level
9
+ self.formatter = formatter || default_formatter
10
+ end
11
+
12
+ def debug(message, **context)
13
+ super(format_message(message, context))
14
+ end
15
+
16
+ def info(message, **context)
17
+ super(format_message(message, context))
18
+ end
19
+
20
+ def warn(message, **context)
21
+ super(format_message(message, context))
22
+ end
23
+
24
+ def error(message, **context)
25
+ super(format_message(message, context))
26
+ end
27
+
28
+ private
29
+
30
+ def format_message(message, context)
31
+ return message if context.empty?
32
+
33
+ {
34
+ message: message,
35
+ **context
36
+ }
37
+ end
38
+
39
+ def default_formatter
40
+ proc do |severity, datetime, progname, msg|
41
+ data = {
42
+ timestamp: datetime.iso8601,
43
+ level: severity,
44
+ progname: progname
45
+ }
46
+
47
+ if msg.is_a?(Hash)
48
+ data.merge!(msg)
49
+ else
50
+ data[:message] = msg
51
+ end
52
+
53
+ "#{JSON.generate(data)}\n"
54
+ end
55
+ end
56
+ end
57
+
58
+ class RequestLogger
59
+ attr_reader :logger, :log_level
60
+
61
+ def initialize(logger:, log_level: :debug)
62
+ @logger = logger
63
+ @log_level = log_level
64
+ end
65
+
66
+ def log_request(method, url, headers, body)
67
+ return unless logger
68
+
69
+ logger.send(log_level, "API Request",
70
+ method: method.to_s.upcase,
71
+ url: url,
72
+ headers: sanitize_headers(headers),
73
+ body: sanitize_body(body)
74
+ )
75
+ end
76
+
77
+ def log_response(response, duration)
78
+ return unless logger
79
+
80
+ logger.send(log_level, "API Response",
81
+ status: response.code,
82
+ duration_ms: (duration * 1000).round(2),
83
+ headers: response.headers,
84
+ body_size: response.body&.bytesize
85
+ )
86
+ end
87
+
88
+ private
89
+
90
+ def sanitize_headers(headers)
91
+ headers.transform_values do |value|
92
+ if value.include?('Bearer')
93
+ value.gsub(/Bearer\s+[\w\-]+/, 'Bearer [REDACTED]')
94
+ else
95
+ value
96
+ end
97
+ end
98
+ end
99
+
100
+ def sanitize_body(body)
101
+ return nil unless body
102
+
103
+ if body.is_a?(String) && body.length > 1000
104
+ "#{body[0..1000]}... (truncated)"
105
+ else
106
+ body
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,26 @@
1
+ module Attio
2
+ module Resources
3
+ class Attributes < Base
4
+ def list(object:, **params)
5
+ validate_object!(object)
6
+ request(:get, "objects/#{object}/attributes", params)
7
+ end
8
+
9
+ def get(object:, id_or_slug:)
10
+ validate_object!(object)
11
+ validate_id_or_slug!(id_or_slug)
12
+ request(:get, "objects/#{object}/attributes/#{id_or_slug}")
13
+ end
14
+
15
+ private
16
+
17
+ def validate_object!(object)
18
+ raise ArgumentError, "Object type is required" if object.nil? || object.to_s.strip.empty?
19
+ end
20
+
21
+ def validate_id_or_slug!(id_or_slug)
22
+ raise ArgumentError, "Attribute ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ module Attio
2
+ module Resources
3
+ # Base class for all API resource classes.
4
+ #
5
+ # This class provides common functionality and request handling
6
+ # for all Attio API resource implementations.
7
+ #
8
+ # @api private
9
+ # @author Ernest Sim
10
+ # @since 1.0.0
11
+ class Base
12
+ # @return [Client] The API client instance
13
+ attr_reader :client
14
+
15
+ # Initialize a new resource instance.
16
+ #
17
+ # @param client [Client] The API client instance
18
+ # @raise [ArgumentError] if client is nil
19
+ def initialize(client)
20
+ raise ArgumentError, "Client is required" unless client
21
+ @client = client
22
+ end
23
+
24
+ private
25
+
26
+ # Make an HTTP request to the API.
27
+ #
28
+ # @param method [Symbol] The HTTP method (:get, :post, :patch, :put, :delete)
29
+ # @param path [String] The API endpoint path
30
+ # @param params [Hash] Request parameters (default: {})
31
+ #
32
+ # @return [Hash] The API response
33
+ # @raise [ArgumentError] if method is unsupported
34
+ #
35
+ # @api private
36
+ def request(method, path, params = {})
37
+ case method
38
+ when :get
39
+ client.connection.get(path, params)
40
+ when :post
41
+ client.connection.post(path, params)
42
+ when :patch
43
+ client.connection.patch(path, params)
44
+ when :put
45
+ client.connection.put(path, params)
46
+ when :delete
47
+ client.connection.delete(path)
48
+ else
49
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ module Attio
2
+ module Resources
3
+ class Lists < Base
4
+ def list(**params)
5
+ request(:get, "lists", params)
6
+ end
7
+
8
+ def get(id:)
9
+ validate_id!(id)
10
+ request(:get, "lists/#{id}")
11
+ end
12
+
13
+ def entries(id:, **params)
14
+ validate_id!(id)
15
+ request(:get, "lists/#{id}/entries", params)
16
+ end
17
+
18
+ def create_entry(id:, data:)
19
+ validate_id!(id)
20
+ validate_data!(data)
21
+ request(:post, "lists/#{id}/entries", data)
22
+ end
23
+
24
+ def get_entry(list_id:, entry_id:)
25
+ validate_list_id!(list_id)
26
+ validate_entry_id!(entry_id)
27
+ request(:get, "lists/#{list_id}/entries/#{entry_id}")
28
+ end
29
+
30
+ def delete_entry(list_id:, entry_id:)
31
+ validate_list_id!(list_id)
32
+ validate_entry_id!(entry_id)
33
+ request(:delete, "lists/#{list_id}/entries/#{entry_id}")
34
+ end
35
+
36
+ private
37
+
38
+ def validate_id!(id)
39
+ raise ArgumentError, "List ID is required" if id.nil? || id.to_s.strip.empty?
40
+ end
41
+
42
+ def validate_list_id!(list_id)
43
+ raise ArgumentError, "List ID is required" if list_id.nil? || list_id.to_s.strip.empty?
44
+ end
45
+
46
+ def validate_entry_id!(entry_id)
47
+ raise ArgumentError, "Entry ID is required" if entry_id.nil? || entry_id.to_s.strip.empty?
48
+ end
49
+
50
+ def validate_data!(data)
51
+ raise ArgumentError, "Data is required" if data.nil?
52
+ raise ArgumentError, "Data must be a hash" unless data.is_a?(Hash)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ module Attio
2
+ module Resources
3
+ class Objects < Base
4
+ def list(**params)
5
+ request(:get, "objects", params)
6
+ end
7
+
8
+ def get(id_or_slug:)
9
+ validate_id_or_slug!(id_or_slug)
10
+ request(:get, "objects/#{id_or_slug}")
11
+ end
12
+
13
+ private
14
+
15
+ def validate_id_or_slug!(id_or_slug)
16
+ raise ArgumentError, "Object ID or slug is required" if id_or_slug.nil? || id_or_slug.to_s.strip.empty?
17
+ end
18
+ end
19
+ end
20
+ end