openapi_kit 0.1.0.pre.1
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +385 -0
- data/lib/openapi_kit/body.rb +71 -0
- data/lib/openapi_kit/codec/boolean.rb +34 -0
- data/lib/openapi_kit/codec/byte.rb +31 -0
- data/lib/openapi_kit/codec/contract.rb +22 -0
- data/lib/openapi_kit/codec/date.rb +32 -0
- data/lib/openapi_kit/codec/date_time.rb +33 -0
- data/lib/openapi_kit/codec/decimal.rb +36 -0
- data/lib/openapi_kit/codec/float.rb +34 -0
- data/lib/openapi_kit/codec/integer.rb +27 -0
- data/lib/openapi_kit/codec/string.rb +26 -0
- data/lib/openapi_kit/codec/uuid.rb +28 -0
- data/lib/openapi_kit/codec.rb +13 -0
- data/lib/openapi_kit/decode.rb +171 -0
- data/lib/openapi_kit/errors.rb +34 -0
- data/lib/openapi_kit/form.rb +25 -0
- data/lib/openapi_kit/optional.rb +67 -0
- data/lib/openapi_kit/rendering.rb +37 -0
- data/lib/openapi_kit/response.rb +21 -0
- data/lib/openapi_kit/runtime.rb +16 -0
- data/lib/openapi_kit/security.rb +122 -0
- data/lib/openapi_kit/version.rb +6 -0
- data/lib/openapi_kit/wire.rb +14 -0
- data/lib/openapi_kit.rb +4 -0
- metadata +112 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/codec/contract"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Codec
|
|
8
|
+
module Float
|
|
9
|
+
extend T::Sig
|
|
10
|
+
extend T::Generic
|
|
11
|
+
extend Contract
|
|
12
|
+
|
|
13
|
+
Value = type_template { { fixed: ::Float } }
|
|
14
|
+
|
|
15
|
+
sig { override.params(value: OpenAPIKit::Wire).returns(::Float) }
|
|
16
|
+
def self.from_wire(value)
|
|
17
|
+
return value.to_f if value.is_a?(::Numeric)
|
|
18
|
+
|
|
19
|
+
if value.is_a?(::String)
|
|
20
|
+
begin
|
|
21
|
+
return Kernel.Float(value)
|
|
22
|
+
rescue ArgumentError, TypeError
|
|
23
|
+
raise DecodeError.new("expected a number, got #{value.inspect}")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
raise DecodeError.new("expected a number, got #{value.inspect}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { override.params(value: ::Float).returns(OpenAPIKit::Wire) }
|
|
31
|
+
def self.to_wire(value) = value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/codec/contract"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Codec
|
|
8
|
+
module Integer
|
|
9
|
+
extend T::Sig
|
|
10
|
+
extend T::Generic
|
|
11
|
+
extend Contract
|
|
12
|
+
|
|
13
|
+
Value = type_template { { fixed: ::Integer } }
|
|
14
|
+
|
|
15
|
+
sig { override.params(value: OpenAPIKit::Wire).returns(::Integer) }
|
|
16
|
+
def self.from_wire(value)
|
|
17
|
+
return value if value.is_a?(::Integer)
|
|
18
|
+
return value.to_i if value.is_a?(::String) && value.match?(/\A[+-]?\d+\z/)
|
|
19
|
+
|
|
20
|
+
raise DecodeError.new("expected an integer, got #{value.inspect}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
sig { override.params(value: ::Integer).returns(OpenAPIKit::Wire) }
|
|
24
|
+
def self.to_wire(value) = value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/codec/contract"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Codec
|
|
8
|
+
module String
|
|
9
|
+
extend T::Sig
|
|
10
|
+
extend T::Generic
|
|
11
|
+
extend Contract
|
|
12
|
+
|
|
13
|
+
Value = type_template { { fixed: ::String } }
|
|
14
|
+
|
|
15
|
+
sig { override.params(value: OpenAPIKit::Wire).returns(::String) }
|
|
16
|
+
def self.from_wire(value)
|
|
17
|
+
return value if value.is_a?(::String)
|
|
18
|
+
|
|
19
|
+
raise DecodeError.new("expected a string, got #{value.inspect}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
sig { override.params(value: ::String).returns(OpenAPIKit::Wire) }
|
|
23
|
+
def self.to_wire(value) = value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/codec/contract"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Codec
|
|
8
|
+
module Uuid
|
|
9
|
+
extend T::Sig
|
|
10
|
+
extend T::Generic
|
|
11
|
+
extend Contract
|
|
12
|
+
|
|
13
|
+
Value = type_template { { fixed: ::String } }
|
|
14
|
+
|
|
15
|
+
PATTERN = T.let(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, Regexp)
|
|
16
|
+
|
|
17
|
+
sig { override.params(value: OpenAPIKit::Wire).returns(::String) }
|
|
18
|
+
def self.from_wire(value)
|
|
19
|
+
return value if value.is_a?(::String) && value.match?(PATTERN)
|
|
20
|
+
|
|
21
|
+
raise DecodeError.new("expected a UUID, got #{value.inspect}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { override.params(value: ::String).returns(OpenAPIKit::Wire) }
|
|
25
|
+
def self.to_wire(value) = value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/codec/contract"
|
|
5
|
+
require "openapi_kit/codec/string"
|
|
6
|
+
require "openapi_kit/codec/integer"
|
|
7
|
+
require "openapi_kit/codec/float"
|
|
8
|
+
require "openapi_kit/codec/decimal"
|
|
9
|
+
require "openapi_kit/codec/boolean"
|
|
10
|
+
require "openapi_kit/codec/date_time"
|
|
11
|
+
require "openapi_kit/codec/date"
|
|
12
|
+
require "openapi_kit/codec/uuid"
|
|
13
|
+
require "openapi_kit/codec/byte"
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "action_dispatch"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Decode
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
sig do
|
|
11
|
+
type_parameters(:T)
|
|
12
|
+
.params(raw: T::Hash[String, T.untyped], key: String,
|
|
13
|
+
block: T.proc.params(value: T.untyped).returns(T.type_parameter(:T)))
|
|
14
|
+
.returns(T.type_parameter(:T))
|
|
15
|
+
end
|
|
16
|
+
def self.required(raw, key, &block)
|
|
17
|
+
raise DecodeError.new("is required", json_pointer: "/#{key}") unless raw.key?(key)
|
|
18
|
+
|
|
19
|
+
value = raw[key]
|
|
20
|
+
raise DecodeError.new("must not be null", json_pointer: "/#{key}") if value.nil?
|
|
21
|
+
|
|
22
|
+
at("/#{key}") { block.call(value) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig do
|
|
26
|
+
type_parameters(:T)
|
|
27
|
+
.params(raw: T::Hash[String, T.untyped], key: String,
|
|
28
|
+
block: T.proc.params(value: T.untyped).returns(T.type_parameter(:T)))
|
|
29
|
+
.returns(T.nilable(T.type_parameter(:T)))
|
|
30
|
+
end
|
|
31
|
+
def self.required_nullable(raw, key, &block)
|
|
32
|
+
raise DecodeError.new("is required", json_pointer: "/#{key}") unless raw.key?(key)
|
|
33
|
+
|
|
34
|
+
value = raw[key]
|
|
35
|
+
return nil if value.nil?
|
|
36
|
+
|
|
37
|
+
at("/#{key}") { block.call(value) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig do
|
|
41
|
+
type_parameters(:T)
|
|
42
|
+
.params(raw: T::Hash[String, T.untyped], key: String, fallback: T.type_parameter(:T),
|
|
43
|
+
block: T.proc.params(value: T.untyped).returns(T.type_parameter(:T)))
|
|
44
|
+
.returns(T.type_parameter(:T))
|
|
45
|
+
end
|
|
46
|
+
def self.defaulted(raw, key, fallback, &block)
|
|
47
|
+
value = raw[key]
|
|
48
|
+
return fallback if value.nil?
|
|
49
|
+
|
|
50
|
+
at("/#{key}") { block.call(value) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig do
|
|
54
|
+
type_parameters(:T)
|
|
55
|
+
.params(raw: T::Hash[String, T.untyped], key: String,
|
|
56
|
+
block: T.proc.params(value: T.untyped).returns(T.type_parameter(:T)))
|
|
57
|
+
.returns(T.nilable(T.type_parameter(:T)))
|
|
58
|
+
end
|
|
59
|
+
def self.optional(raw, key, &block)
|
|
60
|
+
value = raw[key]
|
|
61
|
+
return nil if value.nil?
|
|
62
|
+
|
|
63
|
+
at("/#{key}") { block.call(value) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sig do
|
|
67
|
+
type_parameters(:T)
|
|
68
|
+
.params(raw: T::Hash[String, T.untyped], key: String,
|
|
69
|
+
block: T.proc.params(value: T.untyped).returns(T.type_parameter(:T)))
|
|
70
|
+
.returns(OpenAPIKit::Optional[T.nilable(T.type_parameter(:T))])
|
|
71
|
+
end
|
|
72
|
+
def self.optional_nullable(raw, key, &block)
|
|
73
|
+
return OpenAPIKit::ABSENT unless raw.key?(key)
|
|
74
|
+
|
|
75
|
+
value = raw[key]
|
|
76
|
+
return Present[T.nilable(T.type_parameter(:T))].new(nil) if value.nil?
|
|
77
|
+
|
|
78
|
+
Present[T.nilable(T.type_parameter(:T))].new(at("/#{key}") { block.call(value) })
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sig do
|
|
82
|
+
type_parameters(:T)
|
|
83
|
+
.params(raw: T.untyped, block: T.proc.params(item: T.untyped).returns(T.type_parameter(:T)))
|
|
84
|
+
.returns(T::Array[T.type_parameter(:T)])
|
|
85
|
+
end
|
|
86
|
+
def self.each(raw, &block)
|
|
87
|
+
array(raw).each_with_index.map { |item, index| at("/#{index}") { block.call(item) } }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
sig do
|
|
91
|
+
type_parameters(:T)
|
|
92
|
+
.params(raw: T.untyped, block: T.proc.params(item: T.untyped).returns(T.type_parameter(:T)))
|
|
93
|
+
.returns(T::Hash[String, T.type_parameter(:T)])
|
|
94
|
+
end
|
|
95
|
+
def self.values(raw, &block)
|
|
96
|
+
object(raw).to_h { |key, item| [key, at("/#{key}") { block.call(item) }] }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
sig do
|
|
100
|
+
type_parameters(:T)
|
|
101
|
+
.params(prefix: String, block: T.proc.returns(T.type_parameter(:T)))
|
|
102
|
+
.returns(T.type_parameter(:T))
|
|
103
|
+
end
|
|
104
|
+
def self.at(prefix, &block)
|
|
105
|
+
block.call
|
|
106
|
+
rescue DecodeError => e
|
|
107
|
+
raise e.at(prefix)
|
|
108
|
+
rescue ArgumentError, TypeError => e
|
|
109
|
+
raise DecodeError.new(e.message, json_pointer: prefix)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig do
|
|
113
|
+
params(names: T::Array[String],
|
|
114
|
+
lookup: T.proc.params(name: String).returns(T.untyped))
|
|
115
|
+
.returns(T::Hash[String, T.untyped])
|
|
116
|
+
end
|
|
117
|
+
def self.gather(names, &lookup)
|
|
118
|
+
names.each_with_object({}) do |name, found|
|
|
119
|
+
value = lookup.call(name)
|
|
120
|
+
found[name] = value unless value.nil?
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { params(raw: T.untyped).returns(T::Hash[String, T.untyped]) }
|
|
125
|
+
def self.object(raw)
|
|
126
|
+
return raw if raw.is_a?(Hash)
|
|
127
|
+
|
|
128
|
+
raise DecodeError.new("expected an object, got #{raw.class}")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
sig do
|
|
132
|
+
params(value: T.untyped, name: String,
|
|
133
|
+
candidates: T::Array[T.proc.params(value: T.untyped).returns(T.untyped)])
|
|
134
|
+
.returns(T.untyped)
|
|
135
|
+
end
|
|
136
|
+
def self.first_of(value, name, candidates)
|
|
137
|
+
candidates.each do |candidate|
|
|
138
|
+
return candidate.call(value)
|
|
139
|
+
rescue DecodeError
|
|
140
|
+
next
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
raise DecodeError.new("did not match any member of #{name}")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
sig do
|
|
147
|
+
type_parameters(:Enum)
|
|
148
|
+
.params(enum: T.all(T::Class[T.type_parameter(:Enum)], T.class_of(T::Enum)), value: T.untyped)
|
|
149
|
+
.returns(T.type_parameter(:Enum))
|
|
150
|
+
end
|
|
151
|
+
def self.enum(enum, value)
|
|
152
|
+
enum.try_deserialize(value) ||
|
|
153
|
+
raise(DecodeError.new("expected one of #{enum.values.map(&:serialize).join(", ")}, " \
|
|
154
|
+
"got #{value.inspect}"))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
sig { params(value: T.untyped).returns(::ActionDispatch::Http::UploadedFile) }
|
|
158
|
+
def self.file(value)
|
|
159
|
+
return value if value.is_a?(::ActionDispatch::Http::UploadedFile)
|
|
160
|
+
|
|
161
|
+
raise DecodeError.new("expected an uploaded file, got #{value.class}")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
sig { params(raw: T.untyped).returns(T::Array[T.untyped]) }
|
|
165
|
+
def self.array(raw)
|
|
166
|
+
return raw if raw.is_a?(Array)
|
|
167
|
+
|
|
168
|
+
raise DecodeError.new("expected an array, got #{raw.class}")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module OpenAPIKit
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
class ConfigError < Error; end
|
|
8
|
+
|
|
9
|
+
class SchemaError < Error; end
|
|
10
|
+
|
|
11
|
+
class SecurityError < Error; end
|
|
12
|
+
|
|
13
|
+
class DecodeError < Error
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
ROOT = T.let("", String)
|
|
17
|
+
|
|
18
|
+
sig { returns(String) }
|
|
19
|
+
attr_reader :json_pointer
|
|
20
|
+
|
|
21
|
+
sig { returns(String) }
|
|
22
|
+
attr_reader :detail
|
|
23
|
+
|
|
24
|
+
sig { params(detail: String, json_pointer: String).void }
|
|
25
|
+
def initialize(detail, json_pointer: ROOT)
|
|
26
|
+
@detail = detail
|
|
27
|
+
@json_pointer = json_pointer
|
|
28
|
+
super(json_pointer == ROOT ? detail : "#{json_pointer}: #{detail}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(prefix: String).returns(DecodeError) }
|
|
32
|
+
def at(prefix) = DecodeError.new(detail, json_pointer: "#{prefix}#{json_pointer}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "action_dispatch"
|
|
5
|
+
|
|
6
|
+
require "openapi_kit/wire"
|
|
7
|
+
|
|
8
|
+
module OpenAPIKit
|
|
9
|
+
module Form
|
|
10
|
+
Parts = T.type_alias do
|
|
11
|
+
T::Hash[::String, T.any(OpenAPIKit::Wire, ::ActionDispatch::Http::UploadedFile)]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Contract
|
|
15
|
+
extend T::Sig
|
|
16
|
+
extend T::Generic
|
|
17
|
+
interface!
|
|
18
|
+
|
|
19
|
+
Value = type_member
|
|
20
|
+
|
|
21
|
+
sig { abstract.params(parts: OpenAPIKit::Form::Parts).returns(Value) }
|
|
22
|
+
def from_parts(parts); end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module OpenAPIKit
|
|
5
|
+
module Optional
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend T::Generic
|
|
8
|
+
include Kernel
|
|
9
|
+
abstract!
|
|
10
|
+
sealed!
|
|
11
|
+
|
|
12
|
+
Value = type_member
|
|
13
|
+
|
|
14
|
+
sig { abstract.params(fallback: Value).returns(Value) }
|
|
15
|
+
def value_or(fallback); end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Present
|
|
19
|
+
extend T::Sig
|
|
20
|
+
extend T::Generic
|
|
21
|
+
include Optional
|
|
22
|
+
|
|
23
|
+
Value = type_member
|
|
24
|
+
|
|
25
|
+
sig { returns(Value) }
|
|
26
|
+
attr_reader :value
|
|
27
|
+
|
|
28
|
+
sig { params(value: Value).void }
|
|
29
|
+
def initialize(value)
|
|
30
|
+
@value = value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { override.params(fallback: Value).returns(Value) }
|
|
34
|
+
def value_or(fallback) = value
|
|
35
|
+
|
|
36
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
37
|
+
def ==(other) = other.is_a?(Present) && T.unsafe(other).value == T.unsafe(self).value
|
|
38
|
+
|
|
39
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
40
|
+
def eql?(other) = self == other
|
|
41
|
+
|
|
42
|
+
sig { returns(Integer) }
|
|
43
|
+
def hash = [Present, T.unsafe(self).value].hash
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Absent
|
|
47
|
+
extend T::Sig
|
|
48
|
+
extend T::Generic
|
|
49
|
+
include Optional
|
|
50
|
+
|
|
51
|
+
Value = type_member
|
|
52
|
+
|
|
53
|
+
sig { override.params(fallback: Value).returns(Value) }
|
|
54
|
+
def value_or(fallback) = fallback
|
|
55
|
+
|
|
56
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
57
|
+
def ==(other) = other.is_a?(Absent)
|
|
58
|
+
|
|
59
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
60
|
+
def eql?(other) = self == other
|
|
61
|
+
|
|
62
|
+
sig { returns(Integer) }
|
|
63
|
+
def hash = Absent.hash
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ABSENT = T.let(Absent.new.freeze, Absent[T.untyped])
|
|
67
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/body"
|
|
5
|
+
require "openapi_kit/response"
|
|
6
|
+
|
|
7
|
+
module OpenAPIKit
|
|
8
|
+
module Rendering
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
Controller = T.type_alias do
|
|
12
|
+
T.all(::ActionController::Metal, ::ActionController::Head, ::ActionController::Rendering)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
sig { params(result: OpenAPIKit::Response).void }
|
|
16
|
+
def render_response(result)
|
|
17
|
+
T.bind(self, Controller)
|
|
18
|
+
|
|
19
|
+
case (body = result.to_body)
|
|
20
|
+
when OpenAPIKit::Body::Empty
|
|
21
|
+
head(result.status)
|
|
22
|
+
when OpenAPIKit::Body::Json
|
|
23
|
+
render(json: body.wire, status: result.status, content_type: result.content_type)
|
|
24
|
+
when OpenAPIKit::Body::Stream
|
|
25
|
+
response.headers["Content-Type"] = result.content_type.to_s
|
|
26
|
+
response.status = result.status
|
|
27
|
+
self.response_body = body
|
|
28
|
+
when OpenAPIKit::Body::File
|
|
29
|
+
response.headers["Content-Type"] = result.content_type.to_s
|
|
30
|
+
response.headers["Content-Length"] = body.size.to_s
|
|
31
|
+
response.status = result.status
|
|
32
|
+
response.send_file(body.path.to_s)
|
|
33
|
+
else T.absurd(body)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openapi_kit/body"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Response
|
|
8
|
+
extend T::Sig
|
|
9
|
+
extend T::Helpers
|
|
10
|
+
interface!
|
|
11
|
+
|
|
12
|
+
sig { abstract.returns(::Integer) }
|
|
13
|
+
def status; end
|
|
14
|
+
|
|
15
|
+
sig { abstract.returns(OpenAPIKit::Body) }
|
|
16
|
+
def to_body; end
|
|
17
|
+
|
|
18
|
+
sig { abstract.returns(T.nilable(::String)) }
|
|
19
|
+
def content_type; end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "openapi_kit/version"
|
|
7
|
+
require "openapi_kit/errors"
|
|
8
|
+
require "openapi_kit/wire"
|
|
9
|
+
require "openapi_kit/form"
|
|
10
|
+
require "openapi_kit/body"
|
|
11
|
+
require "openapi_kit/response"
|
|
12
|
+
require "openapi_kit/optional"
|
|
13
|
+
require "openapi_kit/codec"
|
|
14
|
+
require "openapi_kit/decode"
|
|
15
|
+
require "openapi_kit/security"
|
|
16
|
+
require "openapi_kit/rendering"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "action_dispatch"
|
|
5
|
+
|
|
6
|
+
module OpenAPIKit
|
|
7
|
+
module Security
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
sig do
|
|
11
|
+
type_parameters(:Principal)
|
|
12
|
+
.params(attempts: T::Array[T.proc.returns(T.nilable(T.type_parameter(:Principal)))])
|
|
13
|
+
.returns(T.nilable(T.type_parameter(:Principal)))
|
|
14
|
+
end
|
|
15
|
+
def self.first_of(attempts) = attempts.lazy.filter_map(&:call).first
|
|
16
|
+
|
|
17
|
+
sig { params(scheme: Scheme, request: ::ActionDispatch::Request).returns(T.nilable(String)) }
|
|
18
|
+
def self.credential(scheme, request)
|
|
19
|
+
case scheme
|
|
20
|
+
when ApiKey then api_key(scheme, request)
|
|
21
|
+
when Http then authorization(request, scheme.scheme)
|
|
22
|
+
when OAuth2, OpenIDConnect then authorization(request, "bearer")
|
|
23
|
+
else T.absurd(scheme)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { params(scheme: Http, request: ::ActionDispatch::Request).returns(T.nilable([String, String])) }
|
|
28
|
+
def self.basic(scheme, request)
|
|
29
|
+
encoded = credential(scheme, request)
|
|
30
|
+
return nil if encoded.nil?
|
|
31
|
+
|
|
32
|
+
decoded = begin
|
|
33
|
+
encoded.unpack1("m0")
|
|
34
|
+
rescue ArgumentError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
user, password = decoded.to_s.split(":", 2)
|
|
38
|
+
user.nil? || password.nil? ? nil : [user, password]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
sig { params(scheme: ApiKey, request: ::ActionDispatch::Request).returns(T.nilable(String)) }
|
|
42
|
+
def self.api_key(scheme, request)
|
|
43
|
+
location = scheme.location
|
|
44
|
+
case location
|
|
45
|
+
when ApiKeyLocation::Header then present(request.headers[scheme.parameter_name])
|
|
46
|
+
when ApiKeyLocation::Query then present(request.query_parameters[scheme.parameter_name])
|
|
47
|
+
when ApiKeyLocation::Cookie then present(request.cookies[scheme.parameter_name])
|
|
48
|
+
else T.absurd(location)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { params(request: ::ActionDispatch::Request, scheme: String).returns(T.nilable(String)) }
|
|
53
|
+
def self.authorization(request, scheme)
|
|
54
|
+
header = present(request.headers["Authorization"])
|
|
55
|
+
return nil if header.nil?
|
|
56
|
+
|
|
57
|
+
prefix = "#{scheme} "
|
|
58
|
+
return nil unless header.downcase.start_with?(prefix.downcase)
|
|
59
|
+
|
|
60
|
+
present(header[prefix.length..])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
sig { params(value: T.untyped).returns(T.nilable(String)) }
|
|
64
|
+
def self.present(value)
|
|
65
|
+
string = value.to_s
|
|
66
|
+
string.empty? ? nil : string
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
module Scheme
|
|
70
|
+
extend T::Sig
|
|
71
|
+
extend T::Helpers
|
|
72
|
+
include Kernel
|
|
73
|
+
sealed!
|
|
74
|
+
|
|
75
|
+
sig { params(scheme: Scheme).returns(String) }
|
|
76
|
+
def self.name_of(scheme)
|
|
77
|
+
case scheme
|
|
78
|
+
when ApiKey, Http, OAuth2, OpenIDConnect then scheme.name
|
|
79
|
+
else T.absurd(scheme)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class ApiKeyLocation < T::Enum
|
|
85
|
+
enums do
|
|
86
|
+
Query = new("query")
|
|
87
|
+
Header = new("header")
|
|
88
|
+
Cookie = new("cookie")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class ApiKey < T::Struct
|
|
93
|
+
include Scheme
|
|
94
|
+
const :name, String
|
|
95
|
+
const :location, ApiKeyLocation
|
|
96
|
+
const :parameter_name, String
|
|
97
|
+
const :extensions, T::Hash[String, T.untyped], default: {}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class Http < T::Struct
|
|
101
|
+
include Scheme
|
|
102
|
+
const :name, String
|
|
103
|
+
const :scheme, String
|
|
104
|
+
const :bearer_format, T.nilable(String), default: nil
|
|
105
|
+
const :extensions, T::Hash[String, T.untyped], default: {}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class OAuth2 < T::Struct
|
|
109
|
+
include Scheme
|
|
110
|
+
const :name, String
|
|
111
|
+
const :scopes, T::Hash[String, String], default: {}
|
|
112
|
+
const :extensions, T::Hash[String, T.untyped], default: {}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class OpenIDConnect < T::Struct
|
|
116
|
+
include Scheme
|
|
117
|
+
const :name, String
|
|
118
|
+
const :url, String
|
|
119
|
+
const :extensions, T::Hash[String, T.untyped], default: {}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module OpenAPIKit
|
|
5
|
+
# The parsed value model every supported media type leaves behind: application/json and
|
|
6
|
+
# +json, form-urlencoded, and a multipart body's text fields all arrive as these shapes,
|
|
7
|
+
# as do path, query and header values. A codec converts between one of them and a Ruby
|
|
8
|
+
# type, both ways. An uploaded file is the one thing outside it, so it never reaches a
|
|
9
|
+
# codec.
|
|
10
|
+
Wire = T.type_alias do
|
|
11
|
+
T.any(NilClass, String, Integer, Float, T::Boolean,
|
|
12
|
+
T::Array[T.untyped], T::Hash[String, T.untyped])
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/openapi_kit.rb
ADDED