Module: Daytona::Sdk

Defined in:
lib/daytona/sdk/errors.rb,
lib/daytona/sdk.rb,
lib/daytona/sdk/version.rb,
lib/daytona/sdk/file_download_patch.rb

Overview

rubocop:disable Metrics/ModuleLength

Defined Under Namespace

Modules: FileDownloadPatch Classes: A11yUnavailableError, AuthenticationError, BadGatewayError, BadRequestError, CommandAlreadyCompletedError, ConflictError, ConnectionError, ConnectionTimeoutError, Error, FileAccessDeniedError, FileNotFoundError, ForbiddenError, GitAuthFailedError, GitBranchExistsError, GitBranchNotFoundError, GitDirtyWorktreeError, GitMergeConflictError, GitPushRejectedError, GitRepoNotFoundError, GoneError, InternalServerError, LspServerNotInitializedError, NotFoundError, ProcessExecutionTimeoutError, ProcessNotFoundError, RateLimitError, RecordingFfmpegNotFoundError, RecordingStillActiveError, ServerError, ServiceUnavailableError, SessionEndedError, TimeoutError, UnprocessableEntityError

Constant Summary collapse

API_ERROR_CLASSES =

ApiError classes raised by the generated OpenAPI clients. Kept here so the error module can resolve them without depending on ‘sdk.rb`.

[DaytonaApiClient::ApiError, DaytonaToolboxApiClient::ApiError].freeze
SOURCE_API =

Wire-format ‘source` values set by the translation layer when a Daytona service stamps them on the wire envelope. `nil` source means the response did not carry a structured envelope (treat as opaque).

'DAYTONA_API'
SOURCE_DAEMON =
'DAYTONA_DAEMON'
SOURCE_PROXY =
'DAYTONA_PROXY'
ValidationError =
Deprecated.

Use BadRequestError instead. Kept as an alias so existing ‘rescue Daytona::Sdk::ValidationError` blocks keep working.

BadRequestError
STATUS_CODE_TO_ERROR =

Routing tables


{
  400 => BadRequestError,
  401 => AuthenticationError,
  403 => ForbiddenError,
  404 => NotFoundError,
  408 => TimeoutError,
  409 => ConflictError,
  410 => GoneError,
  422 => UnprocessableEntityError,
  429 => RateLimitError,
  500 => InternalServerError,
  502 => BadGatewayError,
  503 => ServiceUnavailableError,
  504 => TimeoutError
}.freeze
CODE_TO_ERROR =

(source, code) tuple → exception class. Resolved BEFORE the status code, so a server-stamped domain code always wins over the generic status class.

{
  # Daemon: git
  [SOURCE_DAEMON, 'GIT_AUTH_FAILED'] => GitAuthFailedError,
  [SOURCE_DAEMON, 'GIT_REPO_NOT_FOUND'] => GitRepoNotFoundError,
  [SOURCE_DAEMON, 'GIT_BRANCH_NOT_FOUND'] => GitBranchNotFoundError,
  [SOURCE_DAEMON, 'GIT_BRANCH_EXISTS'] => GitBranchExistsError,
  [SOURCE_DAEMON, 'GIT_PUSH_REJECTED'] => GitPushRejectedError,
  [SOURCE_DAEMON, 'GIT_DIRTY_WORKTREE'] => GitDirtyWorktreeError,
  [SOURCE_DAEMON, 'GIT_MERGE_CONFLICT'] => GitMergeConflictError,

  # Daemon: filesystem
  [SOURCE_DAEMON, 'FILE_NOT_FOUND'] => FileNotFoundError,
  [SOURCE_DAEMON, 'FILE_ACCESS_DENIED'] => FileAccessDeniedError,

  # Daemon: LSP
  [SOURCE_DAEMON, 'LSP_SERVER_NOT_INITIALIZED'] => LspServerNotInitializedError,

  # Daemon: process / session
  [SOURCE_DAEMON, 'PROCESS_EXECUTION_TIMEOUT'] => ProcessExecutionTimeoutError,
  [SOURCE_DAEMON, 'PROCESS_NOT_FOUND'] => ProcessNotFoundError,
  [SOURCE_DAEMON, 'SESSION_ENDED'] => SessionEndedError,
  [SOURCE_DAEMON, 'COMMAND_ALREADY_COMPLETED'] => CommandAlreadyCompletedError,

  # Daemon: computer-use
  [SOURCE_DAEMON, 'A11Y_UNAVAILABLE'] => A11yUnavailableError,
  [SOURCE_DAEMON, 'RECORDING_STILL_ACTIVE'] => RecordingStillActiveError,
  [SOURCE_DAEMON, 'RECORDING_FFMPEG_NOT_FOUND'] => RecordingFfmpegNotFoundError
}.freeze
VERSION =
'0.184.0.alpha.1'

Class Method Summary collapse

Class Method Details

.api_error_details(error) ⇒ Object

Extract status code, code, source and headers from a raised OpenAPI error. Returns an empty hash when the error is not one of the generated client types.



224
225
226
227
228
229
230
231
232
233
234
# File 'lib/daytona/sdk/errors.rb', line 224

def self.api_error_details(error)
  return {} unless API_ERROR_CLASSES.any? { |c| error.is_a?(c) }

  data = parse_error_body(error.respond_to?(:response_body) ? error.response_body : nil)
  {
    status_code: error.respond_to?(:code) ? error.code : nil,
    code: data[:code],
    source: data[:source],
    headers: error.respond_to?(:response_headers) ? error.response_headers : nil
  }
end

.error_class_for(details) ⇒ Object

Choose the exception class for a parsed error: (source, code) match wins, then HTTP status code, then the base Error.



238
239
240
241
242
243
244
245
246
# File 'lib/daytona/sdk/errors.rb', line 238

def self.error_class_for(details)
  code = details[:code]
  source = details[:source]
  if code && source
    cls = CODE_TO_ERROR[[source, code]]
    return cls if cls
  end
  STATUS_CODE_TO_ERROR.fetch(details[:status_code], Error)
end

.loggerObject

The error hierarchy and translation helpers live in ‘sdk/errors.rb`. This file just provides cross-cutting bits that need to be loaded alongside them.



51
# File 'lib/daytona/sdk.rb', line 51

def self.logger = @logger ||= Logger.new($stdout, level: Logger::INFO)

.parse_error_body(response_body) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/daytona/sdk/errors.rb', line 248

def self.parse_error_body(response_body)
  return {} if response_body.nil? || response_body.empty?

  data = JSON.parse(response_body)
  return {} unless data.is_a?(Hash)

  {
    message: string_or_nil(data['message']) || string_or_nil(data['error']),
    code: string_or_nil(data['code'] || data['error_code']),
    source: string_or_nil(data['source'])
  }
rescue JSON::ParserError
  {}
end

.parsed_message(error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



264
265
266
267
268
269
# File 'lib/daytona/sdk/errors.rb', line 264

def self.parsed_message(error)
  return nil unless API_ERROR_CLASSES.any? { |c| error.is_a?(c) }
  return nil unless error.respond_to?(:response_body)

  parse_error_body(error.response_body)[:message]
end

.string_or_nil(value) ⇒ Object



271
272
273
# File 'lib/daytona/sdk/errors.rb', line 271

def self.string_or_nil(value)
  value.is_a?(String) && !value.empty? ? value : nil
end

.wrap_error(error, prefix = nil) ⇒ Object

Translate an OpenAPI-client error into the most specific Daytona SDK exception. Accepts an optional ‘prefix` that’s prepended to the message for context (e.g. “Failed to create sandbox”). When ‘error` is already an `Sdk::Error` (e.g. raised by the streaming transfer helpers for cancel/timeout), its class is preserved and only the message is prefixed.



208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/daytona/sdk/errors.rb', line 208

def self.wrap_error(error, prefix = nil)
  if error.is_a?(Error)
    message = prefix ? "#{prefix}: #{error.message}" : error.message
    return error.class.new(message, status_code: error.status_code, code: error.code,
                                    source: error.source, headers: error.headers)
  end

  details = api_error_details(error)
  base_message = parsed_message(error) || error.message
  message = prefix ? "#{prefix}: #{base_message}" : base_message
  error_class_for(details).new(message, **details.slice(:status_code, :code, :source, :headers))
end