proc 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f07ae5d056122c93826012896beb760372534d7c2c83193674580517ff31f86
4
- data.tar.gz: 5cd335938ec4121d5170c27518678d81c27dd3b65ff6b0235be72213e05613e2
3
+ metadata.gz: 63d146f97fd1dc28e5ff40ae924ac80e5f34838da4a3bb626b51023c2e23efbc
4
+ data.tar.gz: ebbab25f52bc3344c9fee6e10a00af6ed5022808d05f8516c41dc914987178d6
5
5
  SHA512:
6
- metadata.gz: '08b92c4b033fe05dea3a0db6321c5cea4ce5f8ce3587e3f1b7902b295e7778f95a433e84652f12d71f3eff0e60d2bc53d584cce308bd307e32b54f76ed3213e3'
7
- data.tar.gz: ef90d03381df7f8f016460cc83f15c14b3b1f9768dd5078588e85591e61d46df628fd63fd7e804d6d7e1975c7dd9749642f4b51ecaa5bf1e7c19896f64155d72
6
+ metadata.gz: 06f06ba059619e117f86021bfeda3b5520d6842664d5fb284c77e7920cae38b66d4e82874329243ee0e456216034a07111e04f8b49ffaf6828470eb8a0e7f2fc
7
+ data.tar.gz: d6b1423b158de5320e963c48a988db2936c5c576eb3e02b2ec9d2d675ff54ca6c428e270baaf566935bf8df2fd18b9b59e67449c5ed661d5c57d7a4b75a8f41b
@@ -6,4 +6,12 @@ class Proc
6
6
  def self.connect(authorization, **options)
7
7
  Client.new(authorization, **options)
8
8
  end
9
+
10
+ def self.undefined
11
+ @_undefined ||= Object.new
12
+ end
13
+
14
+ def self.undefined?(value)
15
+ value == undefined
16
+ end
9
17
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ class Argument
5
+ def initialize(name, **options)
6
+ @name = name
7
+ @options = options
8
+ end
9
+
10
+ def serialize
11
+ {"::" => {"name" => @name.to_s}.merge(serialized_options)}
12
+ end
13
+
14
+ def serialized_options
15
+ @options.each_pair.each_with_object({}) { |(key, value), hash|
16
+ hash[key.to_s] = serialize_value(value)
17
+ }
18
+ end
19
+
20
+ private def serialize_value(value)
21
+ if value.respond_to?(:serialize)
22
+ value.serialize
23
+ else
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,8 +4,8 @@ class Proc
4
4
  class Callable
5
5
  attr_reader :proc, :input, :arguments
6
6
 
7
- def initialize(proc, client:, input: nil, arguments: {})
8
- @proc = proc
7
+ def initialize(proc, client:, input: Proc.undefined, arguments: {})
8
+ @proc = proc.to_s
9
9
  @client = client
10
10
  @input = input
11
11
  @arguments = arguments
@@ -17,27 +17,44 @@ class Proc
17
17
  end
18
18
 
19
19
  def call(input = input_omitted = true, **arguments)
20
- @client.call(@proc, input_omitted ? @input : input, **@arguments.merge(arguments))
20
+ callable = self.class.new(
21
+ @proc,
22
+ client: @client,
23
+ input: input_omitted ? @input : input,
24
+ arguments: @arguments.merge(arguments)
25
+ )
26
+
27
+ @client.call(@proc, callable.serialized_input, **callable.serialized_arguments)
21
28
  end
22
29
 
23
30
  def with(input = input_omitted = true, **arguments)
24
- self.class.new(@proc, client: @client, input: input_omitted ? @input : input, arguments: @arguments.merge(arguments))
31
+ self.class.new(
32
+ @proc,
33
+ client: @client,
34
+ input: input_omitted ? @input : input,
35
+ arguments: @arguments.merge(arguments)
36
+ )
25
37
  end
26
38
 
27
39
  def >>(other)
28
- composed = Composition.new(client: @client)
40
+ composed = Composition.new(client: @client, input: @input)
29
41
  composed << self
30
42
  composed << other
31
43
  composed
32
44
  end
33
45
 
34
46
  def serialize
35
- {
36
- "{}" => {
37
- "<<" => serialize_value(@input),
38
- "[]" => [[@proc, serialized_arguments]]
39
- }
40
- }
47
+ wrapped = {"[]" => [[@proc, serialized_arguments]]}
48
+
49
+ unless Proc.undefined?(@input)
50
+ wrapped["<<"] = serialized_input
51
+ end
52
+
53
+ {"{}" => wrapped}
54
+ end
55
+
56
+ def serialized_input
57
+ serialize_value(@input)
41
58
  end
42
59
 
43
60
  def serialized_arguments
@@ -46,6 +63,15 @@ class Proc
46
63
  }
47
64
  end
48
65
 
66
+ def [](proc)
67
+ Callable.new(
68
+ [@proc, proc].join("."),
69
+ client: @client,
70
+ input: @input,
71
+ arguments: @arguments
72
+ )
73
+ end
74
+
49
75
  IGNORE_MISSING = %i[to_hash].freeze
50
76
 
51
77
  def method_missing(name, input = input_omitted = true, **arguments)
@@ -3,17 +3,19 @@
3
3
  require "async"
4
4
  require "oj"
5
5
 
6
+ require_relative "argument"
6
7
  require_relative "callable"
7
8
  require_relative "composition"
8
- require_relative "null_logger"
9
9
 
10
10
  require_relative "http/client"
11
11
 
12
+ Console.logger.off!
13
+
12
14
  class Proc
13
15
  class Error < StandardError
14
16
  end
15
17
 
16
- class ArgumentError < ::ArgumentError
18
+ class Invalid < ::ArgumentError
17
19
  end
18
20
 
19
21
  class Undefined < ::NameError
@@ -22,22 +24,24 @@ class Proc
22
24
  class Unauthorized < Error
23
25
  end
24
26
 
27
+ class Forbidden < Error
28
+ end
29
+
25
30
  class Unavailable < Error
26
31
  end
27
32
 
28
- class RateLimited < Error
33
+ class Limited < Error
29
34
  end
30
35
 
31
36
  class Timeout < Error
32
37
  end
33
38
 
34
- class Client < Http::Client
39
+ class Client
35
40
  def initialize(authorization, scheme: "https", host: "proc.dev")
36
41
  @authorization = authorization
37
42
  @scheme = scheme
38
43
  @host = host
39
-
40
- super()
44
+ @internal = Http::Client.new
41
45
  end
42
46
 
43
47
  def [](proc)
@@ -60,66 +64,73 @@ class Proc
60
64
  @resets_at
61
65
  end
62
66
 
63
-
64
67
  DEFAULT_HEADERS = {
65
68
  "accept" => "application/json",
66
69
  "content-type" => "application/json"
67
70
  }.freeze
68
71
 
69
72
  def call(proc = nil, input = nil, **arguments)
70
- Async(logger: NullLogger) { |task|
71
- body = { "<<" => serialize_value(input) }
72
-
73
- arguments.each_pair do |key, value|
74
- body[key.to_s] = serialize_value(value)
75
- end
76
-
77
- headers = {
78
- "authorization" => "bearer #{@authorization}"
79
- }.merge(DEFAULT_HEADERS)
80
-
81
- begin
82
- response = super(:post, build_uri(proc), headers: headers, body: Oj.dump(body, mode: :json), task: task)
83
-
84
- @remaining = response.headers["x-rate-limit-remaining"].to_s.to_i
85
- @resets_at = Time.at(response.headers["x-rate-limit-reset"].to_s.to_i)
86
-
87
- payload = Oj.load(response.read, mode: :strict)
88
- rescue => error
89
- raise Proc::Unavailable, error.message
90
- ensure
91
- response&.close
92
- end
93
-
94
- case response.status
95
- when 200
96
- payload[">>"]
97
- when 400
98
- raise Proc::ArgumentError, payload.dig("error", "message")
99
- when 403
100
- raise Proc::Unauthorized, payload.dig("error", "message")
101
- when 404
102
- raise Proc::Undefined, payload.dig("error", "message")
103
- when 408
104
- raise Proc::Timeout, payload.dig("error", "message")
105
- when 429
106
- raise Proc::RateLimited, payload.dig("error", "message")
107
- when 500
108
- raise Proc::Error, payload.dig("error", "message")
109
- else
110
- raise Proc::Error, "unhandled"
111
- end
112
- }.wait
73
+ body = {"<>" => true}
74
+
75
+ unless Proc.undefined?(input)
76
+ body["<<"] = serialize_value(input)
77
+ end
78
+
79
+ arguments.each_pair do |key, value|
80
+ body[key.to_s] = serialize_value(value)
81
+ end
82
+
83
+ headers = {
84
+ "authorization" => "bearer #{@authorization}"
85
+ }.merge(DEFAULT_HEADERS)
86
+
87
+ payload = get_payload(proc: proc, headers: headers, body: body)
88
+
89
+ case payload["status"]
90
+ when 200
91
+ payload[">>"]
92
+ when 400
93
+ raise Proc::Invalid, payload.dig("error", "message")
94
+ when 401
95
+ raise Proc::Unauthorized, payload.dig("error", "message")
96
+ when 403
97
+ raise Proc::Forbidden, payload.dig("error", "message")
98
+ when 404
99
+ raise Proc::Undefined, payload.dig("error", "message")
100
+ when 408
101
+ raise Proc::Timeout, payload.dig("error", "message")
102
+ when 429
103
+ raise Proc::Limited, payload.dig("error", "message")
104
+ when 500
105
+ raise Proc::Error, payload.dig("error", "message")
106
+ when 508
107
+ raise Proc::Error, payload.dig("error", "message")
108
+ else
109
+ raise Proc::Error, "unhandled"
110
+ end
113
111
  end
114
112
 
115
- def method_missing(name, input = nil, **arguments)
116
- Callable.new(name, client: self, input: input, arguments: arguments)
113
+ def method_missing(name, input = input_omitted = true, *, **arguments)
114
+ if input_omitted
115
+ Callable.new(name, client: self, arguments: arguments)
116
+ else
117
+ Callable.new(name, client: self, input: input, arguments: arguments)
118
+ end
117
119
  end
118
120
 
119
121
  def respond_to_missing?(name, *)
120
122
  true
121
123
  end
122
124
 
125
+ def argument(name, **options)
126
+ Argument.new(name, **options)
127
+ end
128
+ alias_method :arg, :argument
129
+
130
+ def close
131
+ @internal.close
132
+ end
133
+
123
134
  private def build_uri(proc)
124
135
  host_and_path = File.join(@host, proc.to_s.split(".").join("/"))
125
136
 
@@ -133,5 +144,18 @@ class Proc
133
144
  value
134
145
  end
135
146
  end
147
+
148
+ private def get_payload(proc:, headers:, body:)
149
+ @internal.call(:post, build_uri(proc), headers: headers, body: Oj.dump(body, mode: :custom)) { |response|
150
+ @remaining = response.headers["x-rate-limit-remaining"].to_s.to_i
151
+ @resets_at = Time.at(response.headers["x-rate-limit-reset"].to_s.to_i)
152
+
153
+ payload = Oj.load(response.read, mode: :compat, compat_bigdecimal: true)
154
+ payload["status"] = response.status
155
+ payload
156
+ }
157
+ rescue
158
+ raise Proc::Unavailable
159
+ end
136
160
  end
137
161
  end
@@ -2,9 +2,13 @@
2
2
 
3
3
  class Proc
4
4
  class Composition
5
- def initialize(client:)
5
+ attr_reader :input, :callables, :arguments
6
+
7
+ def initialize(client:, input:, callables: [], arguments: {})
6
8
  @client = client
7
- @callables = []
9
+ @input = input
10
+ @callables = callables
11
+ @arguments = arguments
8
12
  end
9
13
 
10
14
  def initialize_copy(_)
@@ -12,7 +16,23 @@ class Proc
12
16
  end
13
17
 
14
18
  def call(input = input_omitted = true, **arguments)
15
- @client.call("exec", input_omitted ? @callables.first.input : input, proc: serialize)
19
+ callable = self.class.new(
20
+ client: @client,
21
+ input: input_omitted ? @input : input,
22
+ callables: @callables.dup,
23
+ arguments: @arguments.merge(arguments)
24
+ )
25
+
26
+ @client.call("proc.exec", nil, proc: callable.serialize)
27
+ end
28
+
29
+ def with(input = input_omitted = true, **arguments)
30
+ self.class.new(
31
+ client: @client,
32
+ input: input_omitted ? @input : input,
33
+ callables: @callables.dup,
34
+ arguments: @arguments.merge(arguments)
35
+ )
16
36
  end
17
37
 
18
38
  def >>(other)
@@ -22,13 +42,59 @@ class Proc
22
42
  end
23
43
 
24
44
  def <<(callable)
25
- @callables << callable
45
+ case callable
46
+ when Composition
47
+ merge(callable)
48
+ when Callable
49
+ @callables << callable
50
+ end
26
51
  end
27
52
 
28
53
  def serialize
54
+ wrapped = {"[]" => serialized_calls}
55
+
56
+ unless Proc.undefined?(@input)
57
+ wrapped["<<"] = serialized_input
58
+ end
59
+
60
+ {"{}" => wrapped.merge(serialized_arguments)}
61
+ end
62
+
63
+ def serialized_calls
29
64
  @callables.map { |callable|
30
- [callable.proc, callable.serialized_arguments]
65
+ arguments = callable.serialized_arguments
66
+
67
+ unless Proc.undefined?(callable.input)
68
+ arguments["<<"] = callable.serialized_input
69
+ end
70
+
71
+ [callable.proc, arguments]
31
72
  }
32
73
  end
74
+
75
+ def serialized_input
76
+ serialize_value(@input)
77
+ end
78
+
79
+ def serialized_arguments
80
+ @arguments.each_pair.each_with_object({}) { |(key, value), hash|
81
+ hash[key.to_s] = serialize_value(value)
82
+ }
83
+ end
84
+
85
+ def merge(composition)
86
+ raise ArgumentError, "expected a composition" unless composition.is_a?(self.class)
87
+
88
+ @callables.concat(composition.callables)
89
+ @arguments.merge!(composition.arguments)
90
+ end
91
+
92
+ private def serialize_value(value)
93
+ if value.respond_to?(:serialize)
94
+ value.serialize
95
+ else
96
+ value
97
+ end
98
+ end
33
99
  end
34
100
  end
@@ -5,50 +5,45 @@ require "async/http/internet"
5
5
 
6
6
  require "protocol/http/body/streamable"
7
7
 
8
+ require "core/async"
9
+
8
10
  require_relative "request"
9
11
  require_relative "response"
10
12
 
11
- require_relative "../null_logger"
12
-
13
13
  class Proc
14
14
  module Http
15
15
  class Client
16
+ include Is::Async
17
+
16
18
  def initialize
17
19
  @internet = Async::HTTP::Internet.new
18
- @responses = {}
19
20
  end
20
21
 
21
- def call(method, uri, params: {}, headers: {}, body: nil, task: nil)
22
+ def call(method, uri, params: {}, headers: {}, body: nil)
22
23
  request = Request.new(method: method, uri: uri, params: params, headers: headers, body: body)
23
24
 
24
- if task
25
- make_request(request)
26
- else
27
- Async(logger: NullLogger) {
28
- make_request(request)
29
- }.wait
30
- end
25
+ await {
26
+ begin
27
+ response = make_request(request)
28
+
29
+ yield response
30
+ ensure
31
+ response&.close
32
+ end
33
+ }
31
34
  end
32
35
 
33
36
  def close
34
- # TODO: Make sure this works. We should also close / clear after accumulating some amount.
35
- #
36
- @responses.each_value(&:close)
37
- @responses.clear
38
37
  @internet.close
39
38
  end
40
39
 
41
- def count
42
- @responses.count
43
- end
44
-
45
40
  private def make_request(request)
46
41
  async_response = @internet.call(*request.callable)
47
42
  wrap_async_response_for_request(async_response, request)
48
43
  end
49
44
 
50
45
  private def wrap_async_response_for_request(async_response, request)
51
- @responses[async_response] = Response.new(request, async_response)
46
+ Response.new(request, async_response)
52
47
  end
53
48
  end
54
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Proc
4
- VERSION = "0.1.0"
4
+ VERSION = "0.4.0"
5
5
 
6
6
  def self.version
7
7
  VERSION
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-26 00:00:00.000000000 Z
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.52.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: core-async
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: oj
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -47,13 +61,13 @@ files:
47
61
  - LICENSE
48
62
  - README.md
49
63
  - lib/proc.rb
64
+ - lib/proc/argument.rb
50
65
  - lib/proc/callable.rb
51
66
  - lib/proc/client.rb
52
67
  - lib/proc/composition.rb
53
68
  - lib/proc/http/client.rb
54
69
  - lib/proc/http/request.rb
55
70
  - lib/proc/http/response.rb
56
- - lib/proc/null_logger.rb
57
71
  - lib/proc/version.rb
58
72
  homepage: https://proc.dev/
59
73
  licenses:
@@ -74,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  requirements: []
77
- rubygems_version: 3.1.2
91
+ rubygems_version: 3.1.4
78
92
  signing_key:
79
93
  specification_version: 4
80
94
  summary: Proc client library.
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Proc
4
- class NullLogger
5
- class << self
6
- def method_missing(*, **)
7
- end
8
-
9
- def respond_to_missing?(*)
10
- true
11
- end
12
- end
13
- end
14
- end