Class: Rubino::API::Operations::Files::UploadOperation

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/api/operations/files/upload_operation.rb

Overview

POST /v1/files (multipart/form-data, field “file”) Persists a single uploaded file into the sandboxed workspace and returns its descriptor (id, filename, size).

Multipart payload is capped at api.max_upload_bytes (default 50 MiB). The cap is enforced twice: first against the declared Content-Length (cheap reject before any IO), then by wrapping rack.input so a body that lies about its size — or omits Content-Length entirely — still aborts mid-stream before Rack::Multipart can fully buffer it to disk.

Returns:

  • ([Integer, Hash])

    201 + descriptor payload.

Raises:

Defined Under Namespace

Classes: CappedInput

Constant Summary collapse

DEFAULT_MAX_UPLOAD_BYTES =
50 * 1024 * 1024

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(workspace: nil) ⇒ UploadOperation

Accepts an alternate workspace for tests.

Roots at the tool workspace (terminal.cwd || Dir.pwd), the same root tools and attach_file use, so uploaded files land where the agent can read them back. See ReadOperation for the rationale.



98
99
100
# File 'lib/rubino/api/operations/files/upload_operation.rb', line 98

def initialize(workspace: nil)
  @workspace = workspace || ::Rubino::Files::Workspace.new(root: ::Rubino::Tools::Base.workspace_root)
end

Class Method Details

.call(request) ⇒ Object



89
90
91
# File 'lib/rubino/api/operations/files/upload_operation.rb', line 89

def self.call(request)
  new.call(request)
end

Instance Method Details

#call(request) ⇒ Object

Raises:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rubino/api/operations/files/upload_operation.rb', line 102

def call(request)
  content_type = request.env["CONTENT_TYPE"].to_s
  unless content_type.start_with?("multipart/form-data")
    raise ValidationError,
          "content-type must be multipart/form-data"
  end

  limit = max_upload_bytes
  declared = request.env["CONTENT_LENGTH"].to_s
  if !declared.empty? && declared.to_i > limit
    raise PayloadTooLargeError.new(
      "multipart upload exceeds #{limit} bytes",
      details: { limit_bytes: limit }
    )
  end

  params = parse_with_cap(request.env, limit)
  upload = params["file"]
  raise ValidationError, "missing 'file' field" if upload.nil? || !upload.is_a?(Hash)

  descriptor = @workspace.upload(filename: upload[:filename], io: upload[:tempfile])
  [201, { id: descriptor[:id], filename: descriptor[:filename], size: descriptor[:size] }]
end