runapi-core 0.2.7 → 0.2.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f165d79fca32b409a50b34fc13a629eb666e063a1fc2a72e51bd63f1dcf91534
4
- data.tar.gz: 338918ccaeff5eb66a9366f2e56e868f55e8b728580064989453d6942c5babe1
3
+ metadata.gz: 66cc8586f64564c496bb7c4008f5cbaf0a30d1b1528cea036db368a3f9bdc307
4
+ data.tar.gz: 43cb58a1644f09209e1da2e14c6128cf1a334fa245d0d285499b5e4e892a411f
5
5
  SHA512:
6
- metadata.gz: 59a92208fc80fbaeede0a56af09e4a8d41c91a40bdbbd8242635bf762985c1177b340253de20dad30dc44b3a4fd4d76587102c00ceb80b84194c4594a5892b72
7
- data.tar.gz: 96b6055a8ff2a405b500c76418fc91c9b7ebdae676b6832e59052b0700b2500b39813faadcb13204ace7ecea6b754765245c04193b7d31dcf92c95dd0c30a2ad
6
+ metadata.gz: 957bcd8962bcde64fd16281a2b21876ba93b505546277d5e61a0bd898cb9b1ec6369b34ffcbeb0b79df42e5a56fbe1ff1dc8531872f8cd29b49375c5c46a37a7
7
+ data.tar.gz: 43261e4cdcaef883977a9efc32ed752b9000b05602671cabc7471b000a2f01065666746ae09c4be7eeceae622cb7162f84562170eb22214b11b4b1c483e43b0d
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module RunApi
4
6
  module Core
5
7
  class Files
6
8
  include RunApi::Core::ResourceHelpers
7
9
 
8
10
  ENDPOINT = "/api/v1/files"
11
+ PREPARE_ENDPOINT = "#{ENDPOINT}/prepare"
12
+ CONFIRM_ENDPOINT = "#{ENDPOINT}/confirm"
9
13
 
10
14
  class UploadResponse < RunApi::Core::BaseModel
11
15
  required :file_name, String
@@ -25,13 +29,9 @@ module RunApi
25
29
  def create(file: nil, source: nil, file_name: nil, options: nil)
26
30
  validate_source!(file:, source:)
27
31
 
28
- body = if file
29
- multipart_body(file, file_name:)
30
- else
31
- compact_params(source:, file_name:)
32
- end
32
+ return upload_direct(file, file_name:, options:) if file
33
33
 
34
- request(:post, ENDPOINT, body:, options:)
34
+ request(:post, ENDPOINT, body: compact_params(source:, file_name:), options:)
35
35
  end
36
36
 
37
37
  private
@@ -43,15 +43,26 @@ module RunApi
43
43
  raise ArgumentError, "Exactly one source is required: file or source"
44
44
  end
45
45
 
46
- def multipart_body(file, file_name:)
46
+ # Local files upload straight to storage: ask for a pre-authorized target,
47
+ # PUT the bytes there (never through the API), then confirm. The caller still
48
+ # makes a single create call.
49
+ def upload_direct(file, file_name:, options:)
47
50
  path = file_path(file)
48
- filename = file_name || File.basename(path)
49
- Core::MultipartBody.new(
50
- fields: compact_params(file_name: file_name),
51
- files: {
52
- file: Core::MultipartFile.new(path:, filename:)
53
- }
51
+ bytes = File.binread(path)
52
+ prepared = @http.request(
53
+ :post,
54
+ PREPARE_ENDPOINT,
55
+ body: compact_params(
56
+ filename: file_name || File.basename(path),
57
+ byte_size: bytes.bytesize,
58
+ checksum: Digest::MD5.base64digest(bytes)
59
+ ),
60
+ options:
54
61
  )
62
+
63
+ @http.upload(prepared["upload_url"], headers: prepared["headers"], body: bytes)
64
+
65
+ request(:post, CONFIRM_ENDPOINT, body: {signed_id: prepared["signed_id"]}, options:)
55
66
  end
56
67
 
57
68
  def file_path(file)
@@ -54,6 +54,30 @@ module RunApi
54
54
  close_multipart_files(req)
55
55
  end
56
56
 
57
+ # PUT bytes straight to a pre-authorized upload URL with the exact headers
58
+ # issued for it. Skips the base URL, auth, and retries: the URL is single-use
59
+ # and the body is not safe to replay.
60
+ def upload(url, headers:, body:)
61
+ uri = URI.parse(url)
62
+ http = Net::HTTP.new(uri.host, uri.port)
63
+ http.use_ssl = (uri.scheme == "https")
64
+ http.open_timeout = @options.timeout
65
+ http.read_timeout = @options.timeout
66
+
67
+ req = Net::HTTP::Put.new(uri.request_uri)
68
+ headers.each { |key, value| req[key.to_s] = value }
69
+ req.body = body
70
+
71
+ response = http.start { |connection| connection.request(req) }
72
+ return if response.is_a?(Net::HTTPSuccess)
73
+
74
+ raise Error.from_response(response, response.body)
75
+ rescue ::Net::OpenTimeout, ::Net::ReadTimeout => e
76
+ raise TimeoutError, e.message
77
+ rescue ::SocketError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET => e
78
+ raise NetworkError, e.message
79
+ end
80
+
57
81
  private
58
82
 
59
83
  def build_connection
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RunApi
4
4
  module Core
5
- VERSION = "0.2.7"
5
+ VERSION = "0.2.8"
6
6
  end
7
7
 
8
8
  VERSION = Core::VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runapi-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - RunAPI