web_function 0.6.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/lib/web_function/argument.rb +2 -14
- data/lib/web_function/attribute.rb +2 -13
- data/lib/web_function/client.rb +39 -1
- data/lib/web_function/endpoint.rb +7 -15
- data/lib/web_function/object_schema.rb +135 -0
- data/lib/web_function/package.rb +41 -1
- data/lib/web_function/request.rb +1 -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 +23 -0
- data/lib/web_function/version.rb +1 -1
- data/lib/web_function.rb +2 -0
- metadata +35 -2
- data/test.rb +0 -43
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
# Represents a named object definition as described in a Web Function package.
|
|
5
|
+
#
|
|
6
|
+
# Objects are declared in a package under the `"objects"` key and can be referenced as a refined object type
|
|
7
|
+
# (`object.<name>`) anywhere a type is expected. An `object.` reference appears in one of two contexts, which
|
|
8
|
+
# determines which set of properties applies:
|
|
9
|
+
#
|
|
10
|
+
# - Argument context — the object is referenced as an argument's `type`. Its {#arguments} describe its properties.
|
|
11
|
+
# - Attribute context — the object is referenced as an endpoint's `returns` or as an attribute's `type`. Its
|
|
12
|
+
# {#attributes} describe its properties.
|
|
13
|
+
#
|
|
14
|
+
# Because an object MAY be referenced in both contexts within the same package, it MAY define both `arguments` and
|
|
15
|
+
# `attributes`; each set is used only in its matching context.
|
|
16
|
+
#
|
|
17
|
+
# It is named `ObjectSchema` rather than `Object` to avoid clashing with Ruby's built-in `::Object`.
|
|
18
|
+
#
|
|
19
|
+
# See the [object definition documentation][0] on the Web Function website for more details.
|
|
20
|
+
#
|
|
21
|
+
# [0]: https://webfunction.org/package#object-definition
|
|
22
|
+
#
|
|
23
|
+
class ObjectSchema
|
|
24
|
+
# The contexts in which an object may be referenced. Each maps to the member set that applies in that context.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Symbol>]
|
|
27
|
+
#
|
|
28
|
+
CONTEXTS = %i[arguments attributes].freeze
|
|
29
|
+
|
|
30
|
+
def initialize(name:, arguments: [], attributes: [])
|
|
31
|
+
@name = name
|
|
32
|
+
@arguments = arguments.to_h { |a| [a.name, a] }
|
|
33
|
+
@attributes = attributes.to_h { |a| [a.name, a] }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
# Creates a new ObjectSchema from a hash. Typically coming from a {Package}.
|
|
38
|
+
#
|
|
39
|
+
# @param object [Hash] The object hash
|
|
40
|
+
#
|
|
41
|
+
# @return [ObjectSchema, nil] A new ObjectSchema instance, or `nil` if the hash is invalid.
|
|
42
|
+
#
|
|
43
|
+
def from_hash(object)
|
|
44
|
+
unless object.is_a?(Hash)
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless object["name"]
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
new(
|
|
53
|
+
name: object["name"],
|
|
54
|
+
arguments: Argument.from_array(object["arguments"]),
|
|
55
|
+
attributes: Attribute.from_array(object["attributes"]),
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Creates a new ObjectSchema from an array of hashes. Typically coming from a {Package}. Uses
|
|
60
|
+
# {ObjectSchema#from_hash} under the hood.
|
|
61
|
+
#
|
|
62
|
+
# @param objects [Array<Hash>] The object array of hashes
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<ObjectSchema>] A new array of ObjectSchema instances
|
|
65
|
+
#
|
|
66
|
+
def from_array(objects)
|
|
67
|
+
Utils.normalize_array objects do |object|
|
|
68
|
+
from_hash(object)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The name of the object. It is referenced as a refined object type (`object.<name>`) and is unique within a
|
|
74
|
+
# package.
|
|
75
|
+
#
|
|
76
|
+
# @return [String]
|
|
77
|
+
#
|
|
78
|
+
attr_reader :name
|
|
79
|
+
|
|
80
|
+
# The object's properties when it is referenced in an argument context.
|
|
81
|
+
#
|
|
82
|
+
# @return [Array<Argument>]
|
|
83
|
+
#
|
|
84
|
+
def arguments
|
|
85
|
+
@arguments.values
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Looks up a single argument member by name.
|
|
89
|
+
#
|
|
90
|
+
# @param name [String, Symbol] The name of the argument to look up.
|
|
91
|
+
#
|
|
92
|
+
# @return [Argument, nil] The matching argument, or `nil` if none is found.
|
|
93
|
+
#
|
|
94
|
+
def argument(name)
|
|
95
|
+
@arguments[name.to_s]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The object's properties when it is referenced in an attribute context.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Attribute>]
|
|
101
|
+
#
|
|
102
|
+
def attributes
|
|
103
|
+
@attributes.values
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Looks up a single attribute member by name.
|
|
107
|
+
#
|
|
108
|
+
# @param name [String, Symbol] The name of the attribute to look up.
|
|
109
|
+
#
|
|
110
|
+
# @return [Attribute, nil] The matching attribute, or `nil` if none is found.
|
|
111
|
+
#
|
|
112
|
+
def attribute(name)
|
|
113
|
+
@attributes[name.to_s]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The object's properties for the given context.
|
|
117
|
+
#
|
|
118
|
+
# @param context [Symbol] The context to resolve properties for. One of {CONTEXTS}.
|
|
119
|
+
#
|
|
120
|
+
# @raise [ArgumentError] If the context is not one of {CONTEXTS}.
|
|
121
|
+
#
|
|
122
|
+
# @return [Array<Argument>, Array<Attribute>] The properties that apply in the given context.
|
|
123
|
+
#
|
|
124
|
+
def properties(context)
|
|
125
|
+
case context
|
|
126
|
+
when :arguments
|
|
127
|
+
arguments
|
|
128
|
+
when :attributes
|
|
129
|
+
attributes
|
|
130
|
+
else
|
|
131
|
+
raise ArgumentError, "context must be one of #{CONTEXTS.inspect}, got #{context.inspect}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/web_function/package.rb
CHANGED
|
@@ -16,7 +16,7 @@ module WebFunction
|
|
|
16
16
|
include Flaggable
|
|
17
17
|
|
|
18
18
|
def initialize(base_url:, pipeline_url: nil, name: nil, version: nil, docs: nil, flags: [], versions: [],
|
|
19
|
-
endpoints: [], errors: [])
|
|
19
|
+
endpoints: [], errors: [], objects: [])
|
|
20
20
|
@base_url = base_url
|
|
21
21
|
@pipeline_url = pipeline_url
|
|
22
22
|
@name = name
|
|
@@ -26,6 +26,7 @@ module WebFunction
|
|
|
26
26
|
@versions = versions
|
|
27
27
|
@endpoints = endpoints.to_h { |e| [e.name, e] }
|
|
28
28
|
@errors = errors.to_h { |e| [e.code, e] }
|
|
29
|
+
@objects = objects.to_h { |o| [o.name, o] }
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
class << self
|
|
@@ -46,6 +47,7 @@ module WebFunction
|
|
|
46
47
|
versions: Utils.normalize_array_of_strings(package["versions"]),
|
|
47
48
|
endpoints: Endpoint.from_array(package["endpoints"]),
|
|
48
49
|
errors: DocumentedError.from_array(package["errors"]),
|
|
50
|
+
objects: ObjectSchema.from_array(package["objects"]),
|
|
49
51
|
)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
@@ -144,6 +146,44 @@ module WebFunction
|
|
|
144
146
|
@errors[code.to_s]
|
|
145
147
|
end
|
|
146
148
|
|
|
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
|
|
157
|
+
|
|
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]
|
|
175
|
+
|
|
176
|
+
unless object
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if object.properties(context).empty?
|
|
181
|
+
return
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
object
|
|
185
|
+
end
|
|
186
|
+
|
|
147
187
|
# Whether the package is versioned, i.e. whether it declares the `versioned` flag. A versioned package is selected
|
|
148
188
|
# using the `Api-Version` header.
|
|
149
189
|
#
|
data/lib/web_function/request.rb
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
module Type
|
|
5
|
+
class ArrayOf
|
|
6
|
+
attr_reader :base_type, :of
|
|
7
|
+
|
|
8
|
+
def initialize(of)
|
|
9
|
+
@base_type = "array"
|
|
10
|
+
@of = of
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def refinement
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
"#<ArrayOf #{@of.inspect}>"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format(format = :default)
|
|
22
|
+
case format
|
|
23
|
+
when :base
|
|
24
|
+
"array"
|
|
25
|
+
when :compact, :default
|
|
26
|
+
"array<#{@of.format(format)}>"
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError, "unknown format: #{format.inspect}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
format(:default)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
other.is_a?(ArrayOf) && other.of == @of
|
|
38
|
+
end
|
|
39
|
+
alias eql? ==
|
|
40
|
+
|
|
41
|
+
def hash
|
|
42
|
+
[ArrayOf, @of].hash
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def objects
|
|
46
|
+
@of.objects
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def without_refinements
|
|
50
|
+
ArrayOf.new(@of.without_refinements)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def valid?(value)
|
|
54
|
+
value.is_a?(Array) && value.all? { |element| @of.valid?(element) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
module Type
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :base_type, :refinement
|
|
7
|
+
|
|
8
|
+
def initialize(base_type:, refinement: nil)
|
|
9
|
+
@base_type = base_type
|
|
10
|
+
@refinement = refinement
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def inspect
|
|
14
|
+
if @refinement
|
|
15
|
+
"#<#{@base_type} #{@refinement}>"
|
|
16
|
+
else
|
|
17
|
+
"#<#{@base_type}>"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format(format = :default)
|
|
22
|
+
case format
|
|
23
|
+
when :compact
|
|
24
|
+
@refinement || @base_type
|
|
25
|
+
when :base
|
|
26
|
+
@base_type
|
|
27
|
+
when :default
|
|
28
|
+
if @refinement
|
|
29
|
+
"#{@base_type}.#{@refinement}"
|
|
30
|
+
else
|
|
31
|
+
@base_type
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, "unknown format: #{format.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
format(:default)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(other)
|
|
43
|
+
other.is_a?(Base) && other.base_type == @base_type && other.refinement == @refinement
|
|
44
|
+
end
|
|
45
|
+
alias eql? ==
|
|
46
|
+
|
|
47
|
+
def hash
|
|
48
|
+
[Base, @base_type, @refinement].hash
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def objects
|
|
52
|
+
if @base_type == "object" && @refinement
|
|
53
|
+
[@refinement]
|
|
54
|
+
else
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def without_refinements
|
|
60
|
+
return self if @refinement.nil?
|
|
61
|
+
|
|
62
|
+
Base.new(base_type: @base_type)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def valid?(value)
|
|
66
|
+
case @base_type
|
|
67
|
+
when "string"
|
|
68
|
+
value.is_a?(String) && refinement_valid?(value)
|
|
69
|
+
when "number"
|
|
70
|
+
value.is_a?(Numeric) && !value.is_a?(Complex) && refinement_valid?(value)
|
|
71
|
+
when "object"
|
|
72
|
+
value.is_a?(Hash)
|
|
73
|
+
when "boolean"
|
|
74
|
+
value == true || value == false
|
|
75
|
+
when "null"
|
|
76
|
+
value.nil?
|
|
77
|
+
else
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def refinement_valid?(value)
|
|
85
|
+
@refinement.nil? || REFINEMENT_VALIDATORS.fetch(@refinement).call(value)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebFunction
|
|
4
|
+
module Type
|
|
5
|
+
class Union
|
|
6
|
+
attr_reader :members
|
|
7
|
+
|
|
8
|
+
def initialize(members)
|
|
9
|
+
@members = members
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def base_type
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def refinement
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect
|
|
21
|
+
"#<Union #{@members.map(&:inspect).join(" | ")}>"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def format(format = :default)
|
|
25
|
+
@members.map { |member| member.format(format) }.join(" | ")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
format(:default)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(other)
|
|
33
|
+
other.is_a?(Union) && other.members == @members
|
|
34
|
+
end
|
|
35
|
+
alias eql? ==
|
|
36
|
+
|
|
37
|
+
def hash
|
|
38
|
+
[Union, @members].hash
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def objects
|
|
42
|
+
@members.flat_map(&:objects).uniq
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def without_refinements
|
|
46
|
+
Type.union(@members.map(&:without_refinements))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def valid?(value)
|
|
50
|
+
@members.any? { |member| member.valid?(value) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
require_relative "type/base"
|
|
7
|
+
require_relative "type/array_of"
|
|
8
|
+
require_relative "type/union"
|
|
9
|
+
require_relative "type/any"
|
|
10
|
+
|
|
11
|
+
module WebFunction
|
|
12
|
+
module Type
|
|
13
|
+
ALLOWED_REFINEMENTS = {
|
|
14
|
+
"number" => %w[u32 u64 i32 i64 f32 f64 timestamp].freeze,
|
|
15
|
+
"string" => %w[date time datetime uuid base64 email phone url uri ipv4 ipv6 hostname].freeze,
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
# Value-level validators for each refinement, keyed by refinement name. Refinement names are unique across base
|
|
19
|
+
# types, so a flat table is enough. Must stay in sync with {ALLOWED_REFINEMENTS}.
|
|
20
|
+
REFINEMENT_VALIDATORS = {
|
|
21
|
+
"u32" => ->(v) { v.is_a?(Integer) && v.between?(0, 0xFFFFFFFF) },
|
|
22
|
+
"u64" => ->(v) { v.is_a?(Integer) && v.between?(0, 0xFFFFFFFFFFFFFFFF) },
|
|
23
|
+
"i32" => ->(v) { v.is_a?(Integer) && v.between?(-0x80000000, 0x7FFFFFFF) },
|
|
24
|
+
"i64" => ->(v) { v.is_a?(Integer) && v.between?(-0x8000000000000000, 0x7FFFFFFFFFFFFFFF) },
|
|
25
|
+
"f32" => ->(v) { v.is_a?(Numeric) && v.to_f.finite? && v.to_f.abs <= 3.4028235e38 },
|
|
26
|
+
"f64" => ->(v) { v.is_a?(Numeric) && v.to_f.finite? },
|
|
27
|
+
"timestamp" => ->(v) { v.is_a?(Integer) && v >= 0 },
|
|
28
|
+
"date" => ->(v) { v.match?(/\A\d{4}-\d{2}-\d{2}\z/) },
|
|
29
|
+
"time" => ->(v) { v.match?(/\A\d{2}:\d{2}:\d{2}(\.\d+)?\z/) },
|
|
30
|
+
"datetime" => ->(v) { v.match?(/\A\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?\z/) },
|
|
31
|
+
"uuid" => ->(v) { v.match?(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/) },
|
|
32
|
+
"base64" => ->(v) { (v.length % 4).zero? && v.match?(%r{\A[A-Za-z0-9+/]*={0,2}\z}) },
|
|
33
|
+
"email" => ->(v) { v.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/) },
|
|
34
|
+
"phone" => ->(v) { v.match?(/\A\+[1-9]\d{1,14}\z/) },
|
|
35
|
+
"url" => ->(v) { (uri = URI.parse(v)).is_a?(URI::HTTP) && !uri.host.nil? rescue false },
|
|
36
|
+
"uri" => ->(v) { !URI.parse(v).scheme.nil? rescue false },
|
|
37
|
+
"ipv4" => ->(v) { IPAddr.new(v).ipv4? rescue false },
|
|
38
|
+
"ipv6" => ->(v) { IPAddr.new(v).ipv6? rescue false },
|
|
39
|
+
"hostname" => lambda do |v|
|
|
40
|
+
v.match?(%r{\A(?=.{1,253}\z)([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z})
|
|
41
|
+
end,
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def self.parse(raw)
|
|
45
|
+
types = [*raw].map { |type| Type.detect(type) }.compact
|
|
46
|
+
|
|
47
|
+
if types.empty?
|
|
48
|
+
return Type.any
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Type.union(types)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.detect(raw)
|
|
55
|
+
case raw
|
|
56
|
+
when String
|
|
57
|
+
Type.base(raw)
|
|
58
|
+
when Array
|
|
59
|
+
types = raw.map { |type| Type.detect(type) }.compact
|
|
60
|
+
|
|
61
|
+
if types.empty?
|
|
62
|
+
return Type.array(Type.any)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Type.array(Type.union(types))
|
|
66
|
+
else
|
|
67
|
+
return nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.base(type)
|
|
72
|
+
base_type, refinement = type.split(".", 2)
|
|
73
|
+
|
|
74
|
+
case base_type
|
|
75
|
+
when "string"
|
|
76
|
+
if refinement && !ALLOWED_REFINEMENTS["string"].include?(refinement)
|
|
77
|
+
return Type.string
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Type.string(refinement)
|
|
81
|
+
when "number"
|
|
82
|
+
if refinement && !ALLOWED_REFINEMENTS["number"].include?(refinement)
|
|
83
|
+
return Type.number
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Type.number(refinement)
|
|
87
|
+
when "object"
|
|
88
|
+
Type.object(refinement)
|
|
89
|
+
when "array"
|
|
90
|
+
Type.array
|
|
91
|
+
when "boolean"
|
|
92
|
+
Type.boolean
|
|
93
|
+
when "null"
|
|
94
|
+
Type.null
|
|
95
|
+
when "any"
|
|
96
|
+
Type.any
|
|
97
|
+
else
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.string(refinement = nil)
|
|
103
|
+
Base.new(base_type: "string", refinement: refinement)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.number(refinement = nil)
|
|
107
|
+
Base.new(base_type: "number", refinement: refinement)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.object(refinement = nil)
|
|
111
|
+
Base.new(base_type: "object", refinement: refinement)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.array(of = any)
|
|
115
|
+
ArrayOf.new(of)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.boolean
|
|
119
|
+
Base.new(base_type: "boolean")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.null
|
|
123
|
+
Base.new(base_type: "null")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.union(types)
|
|
127
|
+
types = types.uniq
|
|
128
|
+
|
|
129
|
+
if types.length > 1
|
|
130
|
+
Union.new(types)
|
|
131
|
+
else
|
|
132
|
+
types.first
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def self.any
|
|
137
|
+
Any.new
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/web_function/utils.rb
CHANGED
|
@@ -40,5 +40,28 @@ module WebFunction
|
|
|
40
40
|
def normalize_array_of_strings(collection)
|
|
41
41
|
normalize_array(collection) { |item| item.to_s }
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
def get_body_from_url(url, extra_query_params: {})
|
|
45
|
+
url = add_query_params(url, extra_query_params)
|
|
46
|
+
response = ::Excon.get(url, headers: {
|
|
47
|
+
"User-Agent": "webfunction/#{::WebFunction::VERSION}",
|
|
48
|
+
"Accept-Encoding": "gzip",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
response.body
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_query_params(url, params = {})
|
|
55
|
+
uri = ::URI.parse(url)
|
|
56
|
+
existing_params = ::URI.decode_www_form(uri.query || "").to_h
|
|
57
|
+
new_params = params.reject { |_, value| value.nil? }.transform_keys(&:to_s)
|
|
58
|
+
merged_params = existing_params.merge(new_params)
|
|
59
|
+
|
|
60
|
+
unless merged_params.empty?
|
|
61
|
+
uri.query = ::URI.encode_www_form(merged_params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
uri.to_s
|
|
65
|
+
end
|
|
43
66
|
end
|
|
44
67
|
end
|
data/lib/web_function/version.rb
CHANGED
data/lib/web_function.rb
CHANGED
|
@@ -12,9 +12,11 @@ require_relative "web_function/package"
|
|
|
12
12
|
require_relative "web_function/endpoint"
|
|
13
13
|
require_relative "web_function/argument"
|
|
14
14
|
require_relative "web_function/attribute"
|
|
15
|
+
require_relative "web_function/object_schema"
|
|
15
16
|
require_relative "web_function/documented_error"
|
|
16
17
|
require_relative "web_function/pipeline"
|
|
17
18
|
require_relative "web_function/promise"
|
|
19
|
+
require_relative "web_function/type"
|
|
18
20
|
require_relative "web_function/utils"
|
|
19
21
|
|
|
20
22
|
module WebFunction
|