Class: Zotero::Client

Inherits:
Object
  • Object
show all
Includes:
Fields, FileUpload, HTTPErrors, ItemTypes, NetworkErrors, Syncing
Defined in:
lib/zotero/client.rb

Overview

The main HTTP client for interacting with the Zotero Web API v3. Provides authentication, request handling, and access to library operations.

rubocop:disable Metrics/ClassLength

Examples:

Create a client with API key

client = Zotero::Client.new(api_key: 'your-api-key-here')
library = client.user_library(12345)

Constant Summary collapse

BASE_URI =
"https://api.zotero.org"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:) ⇒ Client

Initialize a new Zotero API client.

Parameters:

Raises:

  • (ArgumentError)

    if api_key is nil or empty



39
40
41
# File 'lib/zotero/client.rb', line 39

def initialize(api_key:)
  @api_key = api_key
end

Instance Attribute Details

#api_keyObject (readonly, private)

Returns the value of attribute api_key.



139
140
141
# File 'lib/zotero/client.rb', line 139

def api_key
  @api_key
end

Instance Method Details

#api_clientObject (private) Originally defined in module Syncing

Returns the object that handles API requests. Override in including classes if needed.

Returns:

  • (Object)

    The API client object

#auth_headersObject (protected)



104
105
106
# File 'lib/zotero/client.rb', line 104

def auth_headers
  { "Zotero-API-Key" => api_key }
end

#build_locale_params(locale) ⇒ Object (private) Originally defined in module Fields

#build_request(method, uri, headers, body, request_options) ⇒ Object (private)



186
187
188
189
190
191
# File 'lib/zotero/client.rb', line 186

def build_request(method, uri, headers, body, request_options)
  request = create_request(method, uri)
  set_headers(request, headers)
  set_request_body(request, method, body, headers, request_options) if body
  request
end

#build_request_options(options) ⇒ Object (private)



164
165
166
167
168
169
170
171
172
# File 'lib/zotero/client.rb', line 164

def build_request_options(options)
  {
    headers: options[:headers] || {},
    body: options[:body],
    params: options[:params] || {},
    multipart: options[:multipart],
    format: options[:format]
  }
end

#build_uri(path, params = {}) ⇒ Object (private)



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/zotero/client.rb', line 174

def build_uri(path, params = {})
  base = path.start_with?("http") ? path : "#{BASE_URI}#{path}"
  uri = URI(base)

  unless params.empty?
    query_params = params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join("&")
    uri.query = uri.query ? "#{uri.query}&#{query_params}" : query_params
  end

  uri
end

#build_write_headers(version: nil, write_token: nil) ⇒ Object (protected)



112
113
114
115
116
117
118
# File 'lib/zotero/client.rb', line 112

def build_write_headers(version: nil, write_token: nil)
  headers = auth_headers.merge(default_headers)
  headers["Content-Type"] = "application/json"
  headers["If-Unmodified-Since-Version"] = version.to_s if version
  headers["Zotero-Write-Token"] = write_token if write_token
  headers
end

#calculate_retry_delay(error, attempt, config) ⇒ Object (private)



160
161
162
# File 'lib/zotero/client.rb', line 160

def calculate_retry_delay(error, attempt, config)
  [error.wait_time, config.base_delay * (2**(attempt - 1))].max
end

#create_request(method, uri) ⇒ Object (private)



193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/zotero/client.rb', line 193

def create_request(method, uri)
  request_class = case method
                  when :get then Net::HTTP::Get
                  when :post then Net::HTTP::Post
                  when :put then Net::HTTP::Put
                  when :patch then Net::HTTP::Patch
                  when :delete then Net::HTTP::Delete
                  else raise ArgumentError, "Unsupported HTTP method: #{method}"
                  end

  request_class.new(uri)
end

#creator_fields(locale: nil) ⇒ Array<Hash> Originally defined in module Fields

Get all available creator fields.

Parameters:

  • locale (String) (defaults to: nil)

    Optional locale for localized field names (e.g. 'en-US', 'fr-FR')

Returns:

  • (Array<Hash>)

    Array of creator field definitions with field names and localized labels

#default_headersObject (protected)



108
109
110
# File 'lib/zotero/client.rb', line 108

def default_headers
  { "Zotero-API-Version" => "3" }
end

#deleted_items(since: nil) ⇒ Hash Originally defined in module Syncing

Get items that have been deleted from this library. Only available when included in Library.

Parameters:

  • since (Integer) (defaults to: nil)

    Optional version to get deletions since

Returns:

  • (Hash)

    Object with deleted collections and items arrays

#error_detail(response) ⇒ Object Originally defined in module HTTPErrors

#error_message_for(error) ⇒ Object (private) Originally defined in module NetworkErrors

#external_post(url, multipart_data:) ⇒ Object Originally defined in module FileUpload

#group_library(group_id) ⇒ Library

Get a Library instance for a specific group.

Parameters:

  • group_id (Integer, String)

    The Zotero group ID

Returns:

  • (Library)

    A Library instance for the specified group



55
56
57
# File 'lib/zotero/client.rb', line 55

def group_library(group_id)
  Library.new(client: self, type: :group, id: group_id)
end

#handle_network_errorsObject Originally defined in module NetworkErrors

#handle_response(response, format = nil) ⇒ Object (protected)



120
121
122
123
124
# File 'lib/zotero/client.rb', line 120

def handle_response(response, format = nil)
  return parse_response_body(response, format) if response.code.to_i.between?(200, 299)

  raise_error_for_status(response)
end

#handle_write_response(response) ⇒ Object (protected)



126
127
128
129
130
131
132
133
134
135
# File 'lib/zotero/client.rb', line 126

def handle_write_response(response)
  case response.code.to_i
  when 200
    parse_json_response(response)
  when 204
    true
  else
    raise_error_for_status(response)
  end
end

#http_request(method, path, **options) ⇒ Object (protected)



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/zotero/client.rb', line 92

def http_request(method, path, **options)
  request_options = build_request_options(options)
  uri = build_uri(path, request_options[:params])

  handle_network_errors do
    connection = HTTPConnection.new(uri)
    request = build_request(method, uri, request_options[:headers], request_options[:body], request_options)

    connection.request(request)
  end
end

#item_fields(locale: nil) ⇒ Array<Hash> Originally defined in module Fields

Get all available item fields.

Parameters:

  • locale (String) (defaults to: nil)

    Optional locale for localized field names (e.g. 'en-US', 'fr-FR')

Returns:

  • (Array<Hash>)

    Array of field definitions with field names and localized labels

#item_type_creator_types(item_type) ⇒ Array<Hash> Originally defined in module ItemTypes

Get all creator types available for a specific item type.

Parameters:

  • item_type (String)

    The item type name (e.g. 'book', 'journalArticle')

Returns:

  • (Array<Hash>)

    Array of creator type definitions for the item type

#item_type_fields(item_type, locale: nil) ⇒ Array<Hash> Originally defined in module ItemTypes

Get all fields available for a specific item type.

Parameters:

  • item_type (String)

    The item type name (e.g. 'book', 'journalArticle')

  • locale (String) (defaults to: nil)

    Optional locale for localized field names

Returns:

  • (Array<Hash>)

    Array of field definitions for the item type

#item_types(locale: nil) ⇒ Array<Hash> Originally defined in module ItemTypes

Get all available item types.

Parameters:

  • locale (String) (defaults to: nil)

    Optional locale for localized type names (e.g. 'en-US', 'fr-FR')

Returns:

  • (Array<Hash>)

    Array of item type definitions

#make_get_request(path, params: {}) ⇒ Array, Hash

Make a GET request to the Zotero API. This is the main public interface for read operations.

Parameters:

  • path (String)

    The API endpoint path

  • params (Hash) (defaults to: {})

    Query parameters for the request

Returns:

  • (Array, Hash)

    The parsed response data



65
66
67
68
69
70
71
# File 'lib/zotero/client.rb', line 65

def make_get_request(path, params: {})
  with_retry do
    headers = auth_headers.merge(default_headers)
    response = http_request(:get, path, headers: headers, params: params)
    handle_response(response, params[:format])
  end
end

#make_write_request(method, path, data: nil, options: {}, params: {}) ⇒ Hash, Boolean

Make a write request (POST, PATCH, PUT, DELETE) to the Zotero API. This is the main public interface for write operations.

Parameters:

  • method (Symbol)

    The HTTP method (:post, :patch, :put, :delete)

  • path (String)

    The API endpoint path

  • data (Hash, Array) (defaults to: nil)

    Optional request body data

  • options (Hash) (defaults to: {})

    Write options (version: Integer, write_token: String)

  • params (Hash) (defaults to: {})

    Query parameters for the request

Returns:

  • (Hash, Boolean)

    The parsed response data or success status



82
83
84
85
86
87
88
# File 'lib/zotero/client.rb', line 82

def make_write_request(method, path, data: nil, options: {}, params: {})
  with_retry do
    headers = build_write_headers(version: options[:version], write_token: options[:write_token])
    response = http_request(method, path, headers: headers, body: data, params: params)
    handle_write_response(response)
  end
end

#network_error_classesObject (private) Originally defined in module NetworkErrors

#new_item_template(item_type) ⇒ Hash Originally defined in module ItemTypes

Get a new item template for a specific item type.

Parameters:

  • item_type (String)

    The item type name (e.g. 'book', 'journalArticle')

Returns:

  • (Hash)

    Template object with empty fields for the item type

#parse_json_response(response) ⇒ Object (private)



232
233
234
235
236
237
238
# File 'lib/zotero/client.rb', line 232

def parse_json_response(response)
  return nil if response.body.nil? || response.body.empty?

  JSON.parse(response.body)
rescue JSON::ParserError => e
  raise ParseError, "Failed to parse JSON response: #{e.message}"
end

#parse_response_body(response, format) ⇒ Object (private)



223
224
225
226
227
228
229
230
# File 'lib/zotero/client.rb', line 223

def parse_response_body(response, format)
  case format&.to_s
  when "json", nil
    parse_json_response(response)
  else
    response.body
  end
end

#post_form(path, form_data:, if_match: nil, if_none_match: nil, params: {}) ⇒ Object Originally defined in module FileUpload

#raise_client_error(response) ⇒ Object Originally defined in module HTTPErrors

#raise_error_for_status(response) ⇒ Object Originally defined in module HTTPErrors

#raise_rate_limit_error(response) ⇒ Object Originally defined in module HTTPErrors

Raises:

#raise_server_or_unknown_error(response) ⇒ Object Originally defined in module HTTPErrors

#register_upload(path, upload_key:) ⇒ Object Originally defined in module FileUpload

#request_upload_authorization(path, filename:, md5: nil, mtime: nil, existing_file: false) ⇒ Object Originally defined in module FileUpload

#set_headers(request, headers) ⇒ Object (private)



206
207
208
# File 'lib/zotero/client.rb', line 206

def set_headers(request, headers)
  headers.each { |key, value| request[key] = value }
end

#set_request_body(request, method, body, headers, request_options) ⇒ Object (private)



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/zotero/client.rb', line 210

def set_request_body(request, method, body, headers, request_options)
  return unless %i[post put patch].include?(method)

  if request_options[:multipart]
    request.set_form(body, "multipart/form-data")
  elsif headers["Content-Type"] == "application/x-www-form-urlencoded"
    request.set_form_data(body)
  else
    request.body = body.is_a?(String) ? body : JSON.generate(body)
    request["Content-Type"] = "application/json" unless headers["Content-Type"]
  end
end

#should_retry?(config, attempts) ⇒ Boolean (private)

Returns:

  • (Boolean)


156
157
158
# File 'lib/zotero/client.rb', line 156

def should_retry?(config, attempts)
  config.retry_on_rate_limit && attempts <= config.max_retries
end

#translate_network_error(error) ⇒ Object (private) Originally defined in module NetworkErrors

#user_groups(user_id, format: "versions") ⇒ Hash, Array Originally defined in module Syncing

Get groups for a specific user.

Parameters:

  • user_id (Integer, String)

    The user ID to get groups for

  • format (String) (defaults to: "versions")

    Response format ('versions' or 'json')

Returns:

  • (Hash, Array)

    Groups data in the requested format

#user_library(user_id) ⇒ Library

Get a Library instance for a specific user.

Parameters:

  • user_id (Integer, String)

    The Zotero user ID

Returns:

  • (Library)

    A Library instance for the specified user



47
48
49
# File 'lib/zotero/client.rb', line 47

def user_library(user_id)
  Library.new(client: self, type: :user, id: user_id)
end

#verify_api_keyHash Originally defined in module Syncing

Verify that the current API key is valid.

Returns:

  • (Hash)

    API key information including userID and username

#with_retryObject (private)



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/zotero/client.rb', line 141

def with_retry
  config = HTTPConfig.default
  attempts = 0

  begin
    yield
  rescue RateLimitError => e
    attempts += 1
    raise unless should_retry?(config, attempts)

    sleep(calculate_retry_delay(e, attempts, config))
    retry
  end
end