web_function 0.5.0 → 0.8.0
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 +4 -4
- data/README.md +435 -14
- data/exe/wfn +12 -5
- data/lib/web_function/argument.rb +81 -65
- data/lib/web_function/attribute.rb +78 -65
- data/lib/web_function/client.rb +119 -37
- data/lib/web_function/documented_error.rb +47 -19
- data/lib/web_function/endpoint.rb +181 -171
- data/lib/web_function/flaggable.rb +40 -0
- data/lib/web_function/object_schema.rb +135 -0
- data/lib/web_function/package.rb +169 -40
- data/lib/web_function/pipeline.rb +35 -6
- data/lib/web_function/promise.rb +68 -0
- data/lib/web_function/request.rb +172 -0
- data/lib/web_function/type/any.rb +48 -0
- data/lib/web_function/type/array_of.rb +58 -0
- data/lib/web_function/type/base.rb +89 -0
- data/lib/web_function/type/union.rb +54 -0
- data/lib/web_function/type.rb +140 -0
- data/lib/web_function/utils.rb +67 -0
- data/lib/web_function/version.rb +1 -1
- data/lib/web_function.rb +23 -11
- metadata +38 -1
data/lib/web_function/package.rb
CHANGED
|
@@ -1,67 +1,196 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module WebFunction
|
|
4
|
+
# Organize, document, and validate endpoints. A package facilitates {Endpoint} discovery and integration by providing
|
|
5
|
+
# standardized metadata about them.
|
|
6
|
+
#
|
|
7
|
+
# A package bundles a base URL together with the endpoints it exposes, as well as optional metadata such as a name,
|
|
8
|
+
# version information, top-level documentation, and a list of common errors.
|
|
9
|
+
#
|
|
10
|
+
# See the [package specification][0] on the Web Function website for the full description of every recognized key and
|
|
11
|
+
# its constraints.
|
|
12
|
+
#
|
|
13
|
+
# [0]: https://webfunction.org/package
|
|
14
|
+
#
|
|
4
15
|
class Package
|
|
5
|
-
|
|
6
|
-
@package = package
|
|
7
|
-
end
|
|
16
|
+
include Flaggable
|
|
8
17
|
|
|
9
|
-
def base_url
|
|
10
|
-
|
|
18
|
+
def initialize(base_url:, pipeline_url: nil, name: nil, version: nil, docs: nil, flags: [], versions: [],
|
|
19
|
+
endpoints: [], errors: [], objects: [])
|
|
20
|
+
@base_url = base_url
|
|
21
|
+
@pipeline_url = pipeline_url
|
|
22
|
+
@name = name
|
|
23
|
+
@version = version
|
|
24
|
+
@docs = docs.to_s
|
|
25
|
+
@flags = flags
|
|
26
|
+
@versions = versions
|
|
27
|
+
@endpoints = endpoints.to_h { |e| [e.name, e] }
|
|
28
|
+
@errors = errors.to_h { |e| [e.code, e] }
|
|
29
|
+
@objects = objects.to_h { |o| [o.name, o] }
|
|
11
30
|
end
|
|
12
31
|
|
|
13
|
-
|
|
14
|
-
|
|
32
|
+
class << self
|
|
33
|
+
# Instantiate a new Package from a hash.
|
|
34
|
+
#
|
|
35
|
+
# @param package [Hash] The package hash
|
|
36
|
+
#
|
|
37
|
+
# @return [Package] A new Package instance
|
|
38
|
+
#
|
|
39
|
+
def from_hash(package)
|
|
40
|
+
new(
|
|
41
|
+
base_url: package["base_url"],
|
|
42
|
+
pipeline_url: package["pipeline_url"],
|
|
43
|
+
name: package["name"],
|
|
44
|
+
version: package["version"],
|
|
45
|
+
docs: package["docs"],
|
|
46
|
+
flags: Utils.normalize_array_of_strings(package["flags"]),
|
|
47
|
+
versions: Utils.normalize_array_of_strings(package["versions"]),
|
|
48
|
+
endpoints: Endpoint.from_array(package["endpoints"]),
|
|
49
|
+
errors: DocumentedError.from_array(package["errors"]),
|
|
50
|
+
objects: ObjectSchema.from_array(package["objects"]),
|
|
51
|
+
)
|
|
52
|
+
end
|
|
15
53
|
end
|
|
16
54
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
55
|
+
# The base URL for the package. Endpoint URLs are formed by joining this base URL with each endpoint's name.
|
|
56
|
+
#
|
|
57
|
+
# This is required for the package to be valid and MUST use the HTTP or HTTPS scheme.
|
|
58
|
+
#
|
|
59
|
+
# @return [String]
|
|
60
|
+
#
|
|
61
|
+
attr_reader :base_url
|
|
62
|
+
|
|
63
|
+
# A function pipelining URL used to batch several endpoint invocations into a single request. See the pipelining
|
|
64
|
+
# specification for more information.
|
|
65
|
+
#
|
|
66
|
+
# @return [String, nil]
|
|
67
|
+
#
|
|
68
|
+
attr_reader :pipeline_url
|
|
69
|
+
|
|
70
|
+
# The name of the package.
|
|
71
|
+
#
|
|
72
|
+
# @return [String, nil]
|
|
73
|
+
#
|
|
74
|
+
attr_reader :name
|
|
75
|
+
|
|
76
|
+
# The version that this package describes. An opaque string.
|
|
77
|
+
#
|
|
78
|
+
# This MUST be present when the `versioned` flag is set. See the versioning specification for more information.
|
|
79
|
+
#
|
|
80
|
+
# @return [String, nil]
|
|
81
|
+
#
|
|
82
|
+
attr_reader :version
|
|
21
83
|
|
|
22
|
-
|
|
23
|
-
|
|
84
|
+
# Top-level documentation for the package. It must be formatted as markdown.
|
|
85
|
+
#
|
|
86
|
+
# @return [String]
|
|
87
|
+
#
|
|
88
|
+
attr_reader :docs
|
|
89
|
+
|
|
90
|
+
# The versions that are available. Each entry is an opaque string.
|
|
91
|
+
#
|
|
92
|
+
# This MUST be present when the `versioned` flag is set. See the versioning specification for more information.
|
|
93
|
+
#
|
|
94
|
+
# @return [Array<String>]
|
|
95
|
+
#
|
|
96
|
+
attr_reader :versions
|
|
97
|
+
|
|
98
|
+
# The {Pipeline} for this package, built from {#pipeline_url}, or `nil` when the package does not declare a
|
|
99
|
+
# pipeline URL.
|
|
100
|
+
#
|
|
101
|
+
# @return [Pipeline, nil]
|
|
102
|
+
#
|
|
103
|
+
def pipeline
|
|
104
|
+
unless pipeline_url
|
|
105
|
+
return
|
|
24
106
|
end
|
|
25
|
-
end
|
|
26
107
|
|
|
27
|
-
|
|
28
|
-
@package["docs"].to_s
|
|
108
|
+
Pipeline.new(pipeline_url)
|
|
29
109
|
end
|
|
30
110
|
|
|
111
|
+
# The endpoints declared by this package.
|
|
112
|
+
#
|
|
113
|
+
# @return [Array<Endpoint>]
|
|
114
|
+
#
|
|
31
115
|
def endpoints
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
end
|
|
116
|
+
@endpoints.values
|
|
117
|
+
end
|
|
35
118
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
119
|
+
# Looks up a single endpoint by name. Underscores in the given name are converted to hyphens so that Ruby-style
|
|
120
|
+
# names (e.g. `:find_user_by`) match the hyphenated endpoint names used in packages (e.g. `find-user-by`).
|
|
121
|
+
#
|
|
122
|
+
# @param name [String, Symbol] The name of the endpoint to look up.
|
|
123
|
+
#
|
|
124
|
+
# @return [Endpoint, nil] The matching endpoint, or `nil` if none is found.
|
|
125
|
+
#
|
|
126
|
+
def endpoint(name)
|
|
127
|
+
@endpoints[name.to_s.gsub("_", "-")]
|
|
128
|
+
end
|
|
40
129
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
130
|
+
# The list of common errors that can be returned by any endpoint in this package. Only refer to this list if an
|
|
131
|
+
# endpoint uses the `error_triple` flag. See the error specification for more information.
|
|
132
|
+
#
|
|
133
|
+
# @return [Array<DocumentedError>]
|
|
134
|
+
#
|
|
135
|
+
def errors
|
|
136
|
+
@errors.values
|
|
137
|
+
end
|
|
44
138
|
|
|
45
|
-
|
|
46
|
-
|
|
139
|
+
# Looks up a single common error by its machine-readable code.
|
|
140
|
+
#
|
|
141
|
+
# @param code [String, Symbol] The error code to look up.
|
|
142
|
+
#
|
|
143
|
+
# @return [DocumentedError, nil] The matching error, or `nil` if none is found.
|
|
144
|
+
#
|
|
145
|
+
def error(code)
|
|
146
|
+
@errors[code.to_s]
|
|
47
147
|
end
|
|
48
148
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
149
|
+
# The named object definitions declared by this package. Each can be referenced as a refined object type
|
|
150
|
+
# (`object.<name>`) anywhere a type is expected.
|
|
151
|
+
#
|
|
152
|
+
# @return [Array<ObjectSchema>]
|
|
153
|
+
#
|
|
154
|
+
def objects
|
|
155
|
+
@objects.values
|
|
156
|
+
end
|
|
53
157
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
158
|
+
# Looks up a single object definition by name, resolved for the given context.
|
|
159
|
+
#
|
|
160
|
+
# An `object.` reference appears in either an argument or attribute context, which determines which set of members
|
|
161
|
+
# applies. The `context` selects the member set that is relevant; an object that does not define members for the
|
|
162
|
+
# requested context is treated as absent and `nil` is returned.
|
|
163
|
+
#
|
|
164
|
+
# @param name [String, Symbol] The name of the object to look up.
|
|
165
|
+
# @param context [Symbol] The context the object is referenced in. One of {ObjectSchema::CONTEXTS}
|
|
166
|
+
# (`:arguments` or `:attributes`).
|
|
167
|
+
#
|
|
168
|
+
# @raise [ArgumentError] If the context is not one of {ObjectSchema::CONTEXTS}.
|
|
169
|
+
#
|
|
170
|
+
# @return [ObjectSchema, nil] The matching object, or `nil` if none is found or it defines no members for the
|
|
171
|
+
# given context.
|
|
172
|
+
#
|
|
173
|
+
def object(name, context:)
|
|
174
|
+
object = @objects[name.to_s]
|
|
58
175
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
176
|
+
unless object
|
|
177
|
+
return
|
|
178
|
+
end
|
|
62
179
|
|
|
63
|
-
|
|
180
|
+
if object.properties(context).empty?
|
|
181
|
+
return
|
|
64
182
|
end
|
|
183
|
+
|
|
184
|
+
object
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Whether the package is versioned, i.e. whether it declares the `versioned` flag. A versioned package is selected
|
|
188
|
+
# using the `Api-Version` header.
|
|
189
|
+
#
|
|
190
|
+
# @return [Boolean]
|
|
191
|
+
#
|
|
192
|
+
def versioned?
|
|
193
|
+
flag?("versioned")
|
|
65
194
|
end
|
|
66
195
|
end
|
|
67
196
|
end
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module WebFunction
|
|
4
|
+
# A pipeline is a sequence of steps that are executed in order.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# pipeline = WebFunction::Pipeline.new("https://pipe.example/exec")
|
|
8
|
+
# pipeline.add_step({ url: "https://a", headers: {}, body: {} })
|
|
9
|
+
# pipeline.add_step({ url: "https://b", headers: {}, body: {} })
|
|
10
|
+
# pipeline.execute(returns: :all) # => [{ "a" => 1 }, { "b" => 2 }]
|
|
11
|
+
#
|
|
2
12
|
class Pipeline
|
|
3
13
|
def initialize(url)
|
|
4
14
|
@url = url
|
|
@@ -6,6 +16,12 @@ module WebFunction
|
|
|
6
16
|
@promises = []
|
|
7
17
|
end
|
|
8
18
|
|
|
19
|
+
# Adds a step to the pipeline.
|
|
20
|
+
#
|
|
21
|
+
# @param step [Hash] The step to add
|
|
22
|
+
#
|
|
23
|
+
# @return [Promise] A new Promise instance
|
|
24
|
+
#
|
|
9
25
|
def add_step(step)
|
|
10
26
|
n = @promises.count
|
|
11
27
|
promise = Promise.new(self, "$[#{n}]")
|
|
@@ -16,13 +32,20 @@ module WebFunction
|
|
|
16
32
|
promise
|
|
17
33
|
end
|
|
18
34
|
|
|
35
|
+
# Executes the pipeline.
|
|
36
|
+
#
|
|
37
|
+
# @param returns [String, Symbol] The return type or a JSONPath expression to return a specific value.
|
|
38
|
+
#
|
|
39
|
+
# @return [Object] The response returned by the pipeline.
|
|
40
|
+
#
|
|
19
41
|
def execute(returns: :all)
|
|
20
42
|
case returns
|
|
21
43
|
when :all
|
|
22
|
-
responses =
|
|
44
|
+
responses = Request.execute(@url, args: {
|
|
23
45
|
steps: @steps,
|
|
24
46
|
returns: "$",
|
|
25
|
-
}
|
|
47
|
+
},
|
|
48
|
+
)
|
|
26
49
|
|
|
27
50
|
responses.each_with_index do |response, index|
|
|
28
51
|
@promises[index].value = response
|
|
@@ -32,10 +55,11 @@ module WebFunction
|
|
|
32
55
|
|
|
33
56
|
responses
|
|
34
57
|
when :last
|
|
35
|
-
response =
|
|
58
|
+
response = Request.execute(@url, args: {
|
|
36
59
|
steps: @steps,
|
|
37
60
|
returns: "$[-1:]",
|
|
38
|
-
}
|
|
61
|
+
},
|
|
62
|
+
)
|
|
39
63
|
|
|
40
64
|
@promises.last.value = response
|
|
41
65
|
|
|
@@ -43,10 +67,11 @@ module WebFunction
|
|
|
43
67
|
|
|
44
68
|
response
|
|
45
69
|
else
|
|
46
|
-
response =
|
|
70
|
+
response = Request.execute(@url, args: {
|
|
47
71
|
steps: @steps,
|
|
48
72
|
returns: returns,
|
|
49
|
-
}
|
|
73
|
+
},
|
|
74
|
+
)
|
|
50
75
|
|
|
51
76
|
reset!
|
|
52
77
|
|
|
@@ -54,6 +79,10 @@ module WebFunction
|
|
|
54
79
|
end
|
|
55
80
|
end
|
|
56
81
|
|
|
82
|
+
# Resets the pipeline.
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
#
|
|
57
86
|
def reset!
|
|
58
87
|
@steps = []
|
|
59
88
|
@promises = []
|
data/lib/web_function/promise.rb
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module WebFunction
|
|
4
|
+
# A promise is a placeholder for a value that will be resolved later.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# pipeline = WebFunction::Pipeline.new("https://pipe.example/exec")
|
|
8
|
+
# promise = pipeline.add_step({})
|
|
9
|
+
# promise.resolve # => { "a" => 1 }
|
|
10
|
+
#
|
|
2
11
|
class Promise
|
|
3
12
|
def initialize(pipeline, path)
|
|
4
13
|
@pipeline = pipeline
|
|
@@ -6,19 +15,42 @@ module WebFunction
|
|
|
6
15
|
@value = nil
|
|
7
16
|
end
|
|
8
17
|
|
|
18
|
+
# A path is a JSONPath expression that can be used to resolve a value from a response.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# path = WebFunction::Promise::Path.new("$[0]")
|
|
22
|
+
# path.to_s # => "$[0]"
|
|
23
|
+
# path[0] # => { "a" => 1 }
|
|
24
|
+
#
|
|
9
25
|
class Path
|
|
10
26
|
def initialize(path)
|
|
11
27
|
@path = path
|
|
12
28
|
end
|
|
13
29
|
|
|
30
|
+
# Returns the string representation of the path.
|
|
31
|
+
#
|
|
32
|
+
# @return [String] The string representation of the path
|
|
33
|
+
#
|
|
14
34
|
def to_s
|
|
15
35
|
@path
|
|
16
36
|
end
|
|
17
37
|
|
|
38
|
+
# Returns the JSON representation of the path.
|
|
39
|
+
#
|
|
40
|
+
# @param args [Array] The arguments to pass to the JSON.generate method
|
|
41
|
+
#
|
|
42
|
+
# @return [String] The JSON representation of the path
|
|
43
|
+
#
|
|
18
44
|
def to_json(*args)
|
|
19
45
|
@path.to_json(*args)
|
|
20
46
|
end
|
|
21
47
|
|
|
48
|
+
# Returns a new path with the given key.
|
|
49
|
+
#
|
|
50
|
+
# @param key [String, Symbol, Integer] The key to add to the path
|
|
51
|
+
#
|
|
52
|
+
# @return [Path] A new Path instance
|
|
53
|
+
#
|
|
22
54
|
def [](key)
|
|
23
55
|
case key
|
|
24
56
|
when String, Symbol
|
|
@@ -30,13 +62,27 @@ module WebFunction
|
|
|
30
62
|
end
|
|
31
63
|
end
|
|
32
64
|
|
|
65
|
+
# Mutates the path with the given path.
|
|
66
|
+
#
|
|
67
|
+
# @param path [String] The path to mutate
|
|
68
|
+
#
|
|
69
|
+
# @return [Path] A new Path instance
|
|
70
|
+
#
|
|
33
71
|
def mutate(path)
|
|
34
72
|
Path.new(path)
|
|
35
73
|
end
|
|
36
74
|
end
|
|
37
75
|
|
|
76
|
+
# The value of the promise.
|
|
77
|
+
#
|
|
78
|
+
# @return [Object] The value of the promise
|
|
79
|
+
#
|
|
38
80
|
attr_writer :value
|
|
39
81
|
|
|
82
|
+
# Returns the string representation of the promise.
|
|
83
|
+
#
|
|
84
|
+
# @return [String] The string representation of the promise
|
|
85
|
+
#
|
|
40
86
|
def to_s
|
|
41
87
|
if @value
|
|
42
88
|
@value.to_s
|
|
@@ -45,6 +91,12 @@ module WebFunction
|
|
|
45
91
|
end
|
|
46
92
|
end
|
|
47
93
|
|
|
94
|
+
# Returns the JSON representation of the promise.
|
|
95
|
+
#
|
|
96
|
+
# @param args [Array] The arguments to pass to the JSON.generate method
|
|
97
|
+
#
|
|
98
|
+
# @return [String] The JSON representation of the promise
|
|
99
|
+
#
|
|
48
100
|
def to_json(*args)
|
|
49
101
|
if @value
|
|
50
102
|
@value.to_json(*args)
|
|
@@ -53,6 +105,12 @@ module WebFunction
|
|
|
53
105
|
end
|
|
54
106
|
end
|
|
55
107
|
|
|
108
|
+
# Returns the value of the promise at the given key.
|
|
109
|
+
#
|
|
110
|
+
# @param key [String, Symbol, Integer] The key to resolve
|
|
111
|
+
#
|
|
112
|
+
# @return [Object] The value of the promise at the given key
|
|
113
|
+
#
|
|
56
114
|
def [](key)
|
|
57
115
|
if @value
|
|
58
116
|
@value[key]
|
|
@@ -61,6 +119,12 @@ module WebFunction
|
|
|
61
119
|
end
|
|
62
120
|
end
|
|
63
121
|
|
|
122
|
+
# Returns the value of the promise.
|
|
123
|
+
#
|
|
124
|
+
# @raise [WebFunction::UnresolvedPromiseError] If the promise is not resolved
|
|
125
|
+
#
|
|
126
|
+
# @return [Object] The value of the promise
|
|
127
|
+
#
|
|
64
128
|
def value
|
|
65
129
|
unless @value
|
|
66
130
|
raise WebFunction::UnresolvedPromiseError
|
|
@@ -69,6 +133,10 @@ module WebFunction
|
|
|
69
133
|
@value
|
|
70
134
|
end
|
|
71
135
|
|
|
136
|
+
# Resolves the promise.
|
|
137
|
+
#
|
|
138
|
+
# @return [Object] The value of the promise
|
|
139
|
+
#
|
|
72
140
|
def resolve
|
|
73
141
|
if @value
|
|
74
142
|
return @value
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
# A request allows you to invoke a Web Function endpoint via an HTTP client.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# request = WebFunction::Request.new("https://api.example.com/endpoint")
|
|
8
|
+
# request.execute # => { "a" => 1 }
|
|
9
|
+
#
|
|
10
|
+
class Request
|
|
11
|
+
def initialize(url, bearer_auth: nil, version: nil, args: {})
|
|
12
|
+
@url = url
|
|
13
|
+
@bearer_auth = bearer_auth
|
|
14
|
+
@version = version
|
|
15
|
+
@args = args || {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# The HTTP client used to execute the request.
|
|
20
|
+
#
|
|
21
|
+
# To provide a custom HTTP client instead of the default (which uses Excon),
|
|
22
|
+
# set this to any object responding to #call. For example, a Proc or a lambda.
|
|
23
|
+
#
|
|
24
|
+
# The contract is:
|
|
25
|
+
#
|
|
26
|
+
# client.call(url, headers, body)
|
|
27
|
+
#
|
|
28
|
+
# - url: [String] The full URL to post to (not just the hostname or path).
|
|
29
|
+
# - headers:[Hash<String,String>] HTTP headers, e.g. { "Content-Type" => "application/json" }
|
|
30
|
+
# - body: [String] The JSON body to post.
|
|
31
|
+
#
|
|
32
|
+
# The client must return a two-element Array: [status, body]:
|
|
33
|
+
#
|
|
34
|
+
# - status: [Integer] HTTP status code (e.g. 200, 400, 500)
|
|
35
|
+
# - body: [String] Raw response body as a string
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# WebFunction::Endpoint.http_client = ->(url, headers, args) {
|
|
39
|
+
# http_response = MyHTTP.post(url, headers: headers, body: JSON.generate(args))
|
|
40
|
+
# [http_response.status, http_response.body]
|
|
41
|
+
# }
|
|
42
|
+
#
|
|
43
|
+
attr_accessor :http_client
|
|
44
|
+
|
|
45
|
+
# Executes a request.
|
|
46
|
+
#
|
|
47
|
+
# @param url [String] The URL of the request
|
|
48
|
+
# @param bearer_auth [String] The bearer authentication token
|
|
49
|
+
# @param version [String] The API version to use
|
|
50
|
+
# @param args [Hash] The arguments to send to the request
|
|
51
|
+
#
|
|
52
|
+
# @return [Object] The response returned by the request
|
|
53
|
+
def execute(url, bearer_auth: nil, version: nil, args: {})
|
|
54
|
+
request = new(url, bearer_auth: bearer_auth, version: version, args: args)
|
|
55
|
+
request.execute
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self.http_client = proc do |url, headers, body|
|
|
60
|
+
response = Excon.post(url, headers: headers, body: body)
|
|
61
|
+
[response.status, response.body]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The URL of the request.
|
|
65
|
+
#
|
|
66
|
+
# @return [String] The URL of the request
|
|
67
|
+
#
|
|
68
|
+
attr_reader :url
|
|
69
|
+
|
|
70
|
+
# The bearer authentication token.
|
|
71
|
+
#
|
|
72
|
+
# @return [String] The bearer authentication token
|
|
73
|
+
#
|
|
74
|
+
attr_reader :bearer_auth
|
|
75
|
+
|
|
76
|
+
# The API version to use.
|
|
77
|
+
#
|
|
78
|
+
# @return [String] The API version to use
|
|
79
|
+
#
|
|
80
|
+
attr_reader :version
|
|
81
|
+
|
|
82
|
+
# The arguments to send to the request.
|
|
83
|
+
#
|
|
84
|
+
# @return [Hash] The arguments to send to the request
|
|
85
|
+
#
|
|
86
|
+
attr_reader :args
|
|
87
|
+
|
|
88
|
+
# The headers to send to the request.
|
|
89
|
+
#
|
|
90
|
+
# @return [Hash] The headers to send to the request
|
|
91
|
+
#
|
|
92
|
+
def headers
|
|
93
|
+
headers = {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"Accept": "application/json",
|
|
96
|
+
"User-Agent": "webfunction/#{WebFunction::VERSION}",
|
|
97
|
+
"Accept-Encoding": "gzip",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if @bearer_auth
|
|
101
|
+
headers["Authorization"] = "Bearer #{@bearer_auth}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if @version
|
|
105
|
+
headers["Api-Version"] = @version
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
headers
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the request as a pipeline step.
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash] The request as a pipeline step
|
|
114
|
+
#
|
|
115
|
+
def as_pipeline_step
|
|
116
|
+
{
|
|
117
|
+
url: @url,
|
|
118
|
+
headers: headers,
|
|
119
|
+
body: @args,
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Executes the request.
|
|
124
|
+
#
|
|
125
|
+
# @raise [WebFunction::UnexpectedStatusCodeError] If the status code is not 200 or 400
|
|
126
|
+
# @raise [WebFunction::JsonParseError] If the response is not valid JSON
|
|
127
|
+
# @raise [WebFunction::BadRequestError] If the response is a bad request
|
|
128
|
+
#
|
|
129
|
+
# @return [Object] The response returned by the request
|
|
130
|
+
#
|
|
131
|
+
def execute
|
|
132
|
+
status, body = self.class.http_client.call(@url, headers, JSON.generate(@args))
|
|
133
|
+
|
|
134
|
+
unless [200, 400].include?(status)
|
|
135
|
+
raise WebFunction::UnexpectedStatusCodeError.new("Unexpected status code (#{status})",
|
|
136
|
+
details: {
|
|
137
|
+
status_code: status,
|
|
138
|
+
raw_body: body,
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
begin
|
|
144
|
+
result = JSON.parse(body)
|
|
145
|
+
rescue JSON::ParserError => e
|
|
146
|
+
raise WebFunction::JsonParseError.new(e.message,
|
|
147
|
+
details: {
|
|
148
|
+
status_code: status,
|
|
149
|
+
raw_body: body,
|
|
150
|
+
original_exception: e,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if status == 400
|
|
156
|
+
code = "WFN_BAD_REQUEST_ERROR"
|
|
157
|
+
message = "Bad request"
|
|
158
|
+
details = { body: result }
|
|
159
|
+
|
|
160
|
+
if result.is_a?(Array) && result.length == 3 && result[0].is_a?(String) && result[1].is_a?(String)
|
|
161
|
+
code = result[0]
|
|
162
|
+
message = result[1]
|
|
163
|
+
details = result[2]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
raise WebFunction::BadRequestError.new(message, code: code, details: details)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
result
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
module Type
|
|
5
|
+
class Any
|
|
6
|
+
def base_type
|
|
7
|
+
"any"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def refinement
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inspect
|
|
15
|
+
"#<any>"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def format(_format = :default)
|
|
19
|
+
"any"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
format(:default)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.is_a?(Any)
|
|
28
|
+
end
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
Any.hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def objects
|
|
36
|
+
[]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def without_refinements
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def valid?(_value)
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|