skooma 0.1.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 +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +125 -0
- data/data/oas-3.1/dialect/base.json +25 -0
- data/data/oas-3.1/meta/base.json +96 -0
- data/data/oas-3.1/schema/2022-10-07.json +1441 -0
- data/data/oas-3.1/schema-base/2022-10-07.json +23 -0
- data/lib/skooma/body_parsers.rb +31 -0
- data/lib/skooma/dialects/oas_3_1.rb +41 -0
- data/lib/skooma/inflector.rb +19 -0
- data/lib/skooma/instance.rb +112 -0
- data/lib/skooma/keywords/oas_3_1/dialect/discriminator.rb +13 -0
- data/lib/skooma/keywords/oas_3_1/dialect/example.rb +13 -0
- data/lib/skooma/keywords/oas_3_1/dialect/external_docs.rb +13 -0
- data/lib/skooma/keywords/oas_3_1/dialect/xml.rb +13 -0
- data/lib/skooma/keywords/oas_3_1/schema.rb +32 -0
- data/lib/skooma/keywords/oas_3_1.rb +22 -0
- data/lib/skooma/objects/base/keywords/deprecated.rb +13 -0
- data/lib/skooma/objects/base/keywords/description.rb +13 -0
- data/lib/skooma/objects/base/keywords/security.rb +13 -0
- data/lib/skooma/objects/base/keywords/servers.rb +13 -0
- data/lib/skooma/objects/base/keywords/summary.rb +13 -0
- data/lib/skooma/objects/base/keywords/tags.rb +13 -0
- data/lib/skooma/objects/base.rb +33 -0
- data/lib/skooma/objects/callback.rb +12 -0
- data/lib/skooma/objects/components.rb +30 -0
- data/lib/skooma/objects/header/keywords/content.rb +50 -0
- data/lib/skooma/objects/header/keywords/example.rb +13 -0
- data/lib/skooma/objects/header/keywords/examples.rb +13 -0
- data/lib/skooma/objects/header/keywords/explode.rb +13 -0
- data/lib/skooma/objects/header/keywords/required.rb +19 -0
- data/lib/skooma/objects/header/keywords/schema.rb +19 -0
- data/lib/skooma/objects/header/keywords/style.rb +13 -0
- data/lib/skooma/objects/header.rb +23 -0
- data/lib/skooma/objects/media_type.rb +14 -0
- data/lib/skooma/objects/openapi/keywords/components.rb +23 -0
- data/lib/skooma/objects/openapi/keywords/info.rb +13 -0
- data/lib/skooma/objects/openapi/keywords/json_schema_dialect.rb +21 -0
- data/lib/skooma/objects/openapi/keywords/openapi.rb +24 -0
- data/lib/skooma/objects/openapi/keywords/paths.rb +62 -0
- data/lib/skooma/objects/openapi/keywords/security.rb +13 -0
- data/lib/skooma/objects/openapi/keywords/webhooks.rb +15 -0
- data/lib/skooma/objects/openapi.rb +39 -0
- data/lib/skooma/objects/operation/keywords/callbacks.rb +13 -0
- data/lib/skooma/objects/operation/keywords/operation_id.rb +13 -0
- data/lib/skooma/objects/operation/keywords/parameters.rb +53 -0
- data/lib/skooma/objects/operation/keywords/request_body.rb +21 -0
- data/lib/skooma/objects/operation/keywords/responses.rb +48 -0
- data/lib/skooma/objects/operation.rb +31 -0
- data/lib/skooma/objects/parameter/keywords/allow_empty_value.rb +13 -0
- data/lib/skooma/objects/parameter/keywords/allow_reserved.rb +13 -0
- data/lib/skooma/objects/parameter/keywords/content.rb +21 -0
- data/lib/skooma/objects/parameter/keywords/in.rb +13 -0
- data/lib/skooma/objects/parameter/keywords/name.rb +13 -0
- data/lib/skooma/objects/parameter/keywords/required.rb +19 -0
- data/lib/skooma/objects/parameter/keywords/schema.rb +21 -0
- data/lib/skooma/objects/parameter/keywords/value_parser.rb +80 -0
- data/lib/skooma/objects/parameter.rb +27 -0
- data/lib/skooma/objects/path_item/keywords/base_operation.rb +25 -0
- data/lib/skooma/objects/path_item/keywords/delete.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/get.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/head.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/options.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/parameters.rb +19 -0
- data/lib/skooma/objects/path_item/keywords/patch.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/post.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/put.rb +16 -0
- data/lib/skooma/objects/path_item/keywords/trace.rb +16 -0
- data/lib/skooma/objects/path_item.rb +28 -0
- data/lib/skooma/objects/ref_base.rb +33 -0
- data/lib/skooma/objects/request_body/keywords/required.rb +19 -0
- data/lib/skooma/objects/request_body.rb +17 -0
- data/lib/skooma/objects/response/keywords/content.rb +51 -0
- data/lib/skooma/objects/response/keywords/headers.rb +37 -0
- data/lib/skooma/objects/response/keywords/links.rb +13 -0
- data/lib/skooma/objects/response.rb +18 -0
- data/lib/skooma/output_format.rb +35 -0
- data/lib/skooma/rspec.rb +166 -0
- data/lib/skooma/validators/double.rb +13 -0
- data/lib/skooma/validators/float.rb +13 -0
- data/lib/skooma/validators/int_32.rb +15 -0
- data/lib/skooma/validators/int_64.rb +15 -0
- data/lib/skooma/version.rb +5 -0
- data/lib/skooma.rb +26 -0
- metadata +161 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Delete < BaseOperation
|
|
8
|
+
self.key = "options"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Get < BaseOperation
|
|
8
|
+
self.key = "get"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Head < BaseOperation
|
|
8
|
+
self.key = "head"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Options < BaseOperation
|
|
8
|
+
self.key = "options"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Parameters < JSONSkooma::Keywords::Base
|
|
8
|
+
self.key = "parameters"
|
|
9
|
+
self.value_schema = :array_of_schemas
|
|
10
|
+
self.schema_value_class = Objects::Parameter
|
|
11
|
+
|
|
12
|
+
def evaluate(instance, result)
|
|
13
|
+
result.skip_assertion
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Patch < BaseOperation
|
|
8
|
+
self.key = "patch"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Post < BaseOperation
|
|
8
|
+
self.key = "post"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Put < BaseOperation
|
|
8
|
+
self.key = "put"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class PathItem
|
|
6
|
+
module Keywords
|
|
7
|
+
class Trace < BaseOperation
|
|
8
|
+
self.key = "trace"
|
|
9
|
+
self.depends_on = %w[parameters]
|
|
10
|
+
self.value_schema = :schema
|
|
11
|
+
self.schema_value_class = Objects::Operation
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
# Describes the operations available on a single path.
|
|
6
|
+
# https://spec.openapis.org/oas/v3.1.0#path-item-object
|
|
7
|
+
class PathItem < RefBase
|
|
8
|
+
def kw_classes
|
|
9
|
+
[
|
|
10
|
+
Base::Keywords::Summary,
|
|
11
|
+
Base::Keywords::Description,
|
|
12
|
+
|
|
13
|
+
Keywords::Get,
|
|
14
|
+
Keywords::Put,
|
|
15
|
+
Keywords::Post,
|
|
16
|
+
Keywords::Delete,
|
|
17
|
+
Keywords::Options,
|
|
18
|
+
Keywords::Head,
|
|
19
|
+
Keywords::Patch,
|
|
20
|
+
Keywords::Trace,
|
|
21
|
+
|
|
22
|
+
Base::Keywords::Servers,
|
|
23
|
+
Keywords::Parameters
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
# A simple object to allow referencing other components in the OpenAPI document.
|
|
6
|
+
# https://spec.openapis.org/oas/v3.1.0#referenceObject
|
|
7
|
+
class RefBase < Base
|
|
8
|
+
# This object cannot be extended with additional properties
|
|
9
|
+
# and any properties added SHALL be ignored.
|
|
10
|
+
def resolve_keywords(value)
|
|
11
|
+
return super unless value.key?("$ref")
|
|
12
|
+
|
|
13
|
+
resolve_ref_keywords(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ref_kw_classes
|
|
17
|
+
[
|
|
18
|
+
JSONSkooma::Keywords::Core::Ref,
|
|
19
|
+
Base::Keywords::Summary,
|
|
20
|
+
Base::Keywords::Description
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolve_ref_keywords(value)
|
|
25
|
+
ref_kw_classes.each do |kw_class|
|
|
26
|
+
next unless value.key?(kw_class.key)
|
|
27
|
+
|
|
28
|
+
add_keyword(kw_class.new(self, value[kw_class.key]))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class RequestBody
|
|
6
|
+
module Keywords
|
|
7
|
+
class Required < JSONSkooma::Keywords::Base
|
|
8
|
+
self.key = "required"
|
|
9
|
+
|
|
10
|
+
def evaluate(instance, result)
|
|
11
|
+
if json.value && instance["body"].value.nil?
|
|
12
|
+
result.failure("Body is required")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
# Describes a single request body.
|
|
6
|
+
# https://spec.openapis.org/oas/v3.1.0#request-body-object
|
|
7
|
+
class RequestBody < RefBase
|
|
8
|
+
def kw_classes
|
|
9
|
+
[
|
|
10
|
+
Base::Keywords::Description,
|
|
11
|
+
Skooma::Objects::Response::Keywords::Content,
|
|
12
|
+
Keywords::Required
|
|
13
|
+
]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class Response
|
|
6
|
+
module Keywords
|
|
7
|
+
class Content < JSONSkooma::Keywords::Base
|
|
8
|
+
self.key = "content"
|
|
9
|
+
self.value_schema = :object_of_schemas
|
|
10
|
+
self.schema_value_class = Objects::MediaType
|
|
11
|
+
|
|
12
|
+
def evaluate(instance, result)
|
|
13
|
+
return result.discard unless instance["body"].value
|
|
14
|
+
|
|
15
|
+
media_type = instance["headers"]&.[]("Content-Type")&.split(";")&.first&.strip&.downcase
|
|
16
|
+
media_type_object, matched_media_type = find_media_type(media_type)
|
|
17
|
+
|
|
18
|
+
return result.failure("Media type #{media_type} not found") unless media_type_object
|
|
19
|
+
|
|
20
|
+
result.annotate(matched_media_type)
|
|
21
|
+
result.call(instance, matched_media_type) do |media_type_result|
|
|
22
|
+
media_type_object.evaluate(instance["body"], media_type_result)
|
|
23
|
+
|
|
24
|
+
result.failure("Invalid content") unless media_type_result.passed?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# The key is a media type or media type range and the value describes it.
|
|
31
|
+
# For requests that match multiple keys, only the most specific key is applicable.
|
|
32
|
+
# e.g. text/plain overrides text/*
|
|
33
|
+
def find_media_type(media_type)
|
|
34
|
+
matched_media_type =
|
|
35
|
+
if json.key?(media_type)
|
|
36
|
+
media_type
|
|
37
|
+
elsif media_type &&
|
|
38
|
+
(key = "#{media_type.split("/").first}/*") &&
|
|
39
|
+
json.key?(key)
|
|
40
|
+
key
|
|
41
|
+
elsif json.key?("*/*")
|
|
42
|
+
"*/*"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
[json[matched_media_type], matched_media_type]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
class Response
|
|
6
|
+
module Keywords
|
|
7
|
+
class Headers < JSONSkooma::Keywords::Base
|
|
8
|
+
self.key = "headers"
|
|
9
|
+
self.value_schema = :object_of_schemas
|
|
10
|
+
self.schema_value_class = Objects::Header
|
|
11
|
+
|
|
12
|
+
def evaluate(instance, result)
|
|
13
|
+
errors = []
|
|
14
|
+
json.each do |key, schema|
|
|
15
|
+
next if ignored_key?(key)
|
|
16
|
+
|
|
17
|
+
result.call(instance["headers"], key) do |subresult|
|
|
18
|
+
schema.evaluate(instance["headers"][key], subresult)
|
|
19
|
+
|
|
20
|
+
errors << key unless subresult.passed?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
return if errors.empty?
|
|
24
|
+
|
|
25
|
+
result.failure("The following headers are invalid: #{errors}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def ignored_key?(key)
|
|
31
|
+
%w[accept content-type authorization].include?(key.downcase)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skooma
|
|
4
|
+
module Objects
|
|
5
|
+
# Describes a single response from an API Operation.
|
|
6
|
+
# https://spec.openapis.org/oas/v3.1.0#responseObject
|
|
7
|
+
class Response < RefBase
|
|
8
|
+
def kw_classes
|
|
9
|
+
[
|
|
10
|
+
Base::Keywords::Description,
|
|
11
|
+
Keywords::Headers,
|
|
12
|
+
Keywords::Content,
|
|
13
|
+
Keywords::Links
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Skooma
|
|
2
|
+
module OutputFormat
|
|
3
|
+
class << self
|
|
4
|
+
def call(result, **_options)
|
|
5
|
+
return {"valid" => true} if result.valid?
|
|
6
|
+
|
|
7
|
+
node_data(result, true)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def node_data(node, first = false)
|
|
13
|
+
data = {
|
|
14
|
+
"instanceLocation" => node.instance.path.to_s,
|
|
15
|
+
"relativeKeywordLocation" => node.relative_path.to_s,
|
|
16
|
+
"keywordLocation" => node.path.to_s
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
child_data = node.children.filter_map do |_, child|
|
|
20
|
+
node_data(child) unless child.valid?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if first || child_data.length > 1
|
|
24
|
+
data["errors"] = child_data
|
|
25
|
+
elsif child_data.length == 1
|
|
26
|
+
data = child_data[0]
|
|
27
|
+
elsif node.error
|
|
28
|
+
data["error"] = node.error
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
data
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/skooma/rspec.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Skooma
|
|
6
|
+
# RSpec matchers for OpenAPI schema validation
|
|
7
|
+
# @example
|
|
8
|
+
# require "skooma/rspec"
|
|
9
|
+
# # ...
|
|
10
|
+
# RSpec.configure do |config|
|
|
11
|
+
# # ...
|
|
12
|
+
# config.include Skooma::RSpec[Rails.root.join("docs", "openapi.yml")], type: :request
|
|
13
|
+
# end
|
|
14
|
+
class RSpec < Module
|
|
15
|
+
class << self
|
|
16
|
+
alias_method :[], :new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Mapper
|
|
20
|
+
PLAIN_HEADERS = %w[CONTENT_LENGTH CONTENT_TYPE].freeze
|
|
21
|
+
REGEXP_HTTP = /^HTTP_/.freeze
|
|
22
|
+
|
|
23
|
+
def mapped_response(response: true, request: true)
|
|
24
|
+
result = {
|
|
25
|
+
"method" => env["REQUEST_METHOD"].downcase,
|
|
26
|
+
"path" => env["PATH_INFO"]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if response
|
|
30
|
+
result["response"] = {
|
|
31
|
+
"status" => response_data[0],
|
|
32
|
+
"headers" => response_data[1],
|
|
33
|
+
"body" => response_data[2]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if request
|
|
38
|
+
result["request"] = {
|
|
39
|
+
"query" => env["rack.request.query_string"] || env["QUERY_STRING"],
|
|
40
|
+
"headers" => env.select { |k, _| k.start_with?("HTTP_") || PLAIN_HEADERS.include?(k) }.transform_keys { |k| k.sub(REGEXP_HTTP, "").split("_").map(&:capitalize).join("-") },
|
|
41
|
+
"body" => env["RAW_POST_DATA"]
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def env
|
|
49
|
+
request.env
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def response_data
|
|
53
|
+
[response.status, response.headers.to_h, response.body]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(openapi_path, base_uri: "https://skoomarb.dev/")
|
|
58
|
+
super()
|
|
59
|
+
|
|
60
|
+
pathname = Pathname.new(openapi_path)
|
|
61
|
+
|
|
62
|
+
registry = Skooma.create_registry
|
|
63
|
+
registry.add_source(
|
|
64
|
+
base_uri,
|
|
65
|
+
JSONSkooma::Sources::Local.new(pathname.dirname.to_s)
|
|
66
|
+
)
|
|
67
|
+
schema = registry.schema(URI.parse("#{base_uri}#{pathname.basename}"), schema_class: Skooma::Objects::OpenAPI)
|
|
68
|
+
|
|
69
|
+
include Mapper
|
|
70
|
+
|
|
71
|
+
define_method :skooma_openapi_schema do
|
|
72
|
+
schema
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
::RSpec::Matchers.define(:conform_schema) do |expected_status|
|
|
78
|
+
match do
|
|
79
|
+
next false unless response.status == expected_status
|
|
80
|
+
|
|
81
|
+
@result = skooma_openapi_schema.evaluate(mapped_response)
|
|
82
|
+
@result.valid?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
description do
|
|
86
|
+
"conform schema with #{expected_status} response code"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
failure_message do
|
|
90
|
+
<<~MSG
|
|
91
|
+
ENV:
|
|
92
|
+
#{PP.pp(mapped_response, +"")}
|
|
93
|
+
|
|
94
|
+
Validation Result:
|
|
95
|
+
#{@result ? PP.pp(@result.output(:skooma), +"") : "Expected #{expected_status} status code"}
|
|
96
|
+
MSG
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
::RSpec::Matchers.define(:conform_request_schema) do
|
|
101
|
+
match do
|
|
102
|
+
@result = skooma_openapi_schema.evaluate(mapped_response(response: false))
|
|
103
|
+
@result.valid?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
description do
|
|
107
|
+
"conform request schema"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
failure_message do
|
|
111
|
+
<<~MSG
|
|
112
|
+
ENV:
|
|
113
|
+
#{PP.pp(mapped_response, +"")}
|
|
114
|
+
|
|
115
|
+
Validation Result:
|
|
116
|
+
#{@result ? PP.pp(@result.output(:skooma), +"") : "Expected #{expected_status} status code"}
|
|
117
|
+
MSG
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
::RSpec::Matchers.define(:conform_response_schema) do |expected_status|
|
|
122
|
+
match do
|
|
123
|
+
next false unless response.status == expected_status
|
|
124
|
+
|
|
125
|
+
@result = skooma_openapi_schema.evaluate(mapped_response(request: false))
|
|
126
|
+
@result.valid?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
description do
|
|
130
|
+
"conform response schema with #{expected_status} response code"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
failure_message do
|
|
134
|
+
<<~MSG
|
|
135
|
+
ENV:
|
|
136
|
+
#{PP.pp(mapped_response, +"")}
|
|
137
|
+
|
|
138
|
+
Validation Result:
|
|
139
|
+
#{@result ? PP.pp(@result.output(:skooma), +"") : "Expected #{expected_status} status code"}
|
|
140
|
+
MSG
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
::RSpec::Matchers.define(:be_valid_document) do
|
|
145
|
+
match do |actual|
|
|
146
|
+
@actual = actual
|
|
147
|
+
next false unless comparable?
|
|
148
|
+
|
|
149
|
+
@result = actual.validate
|
|
150
|
+
@result.valid?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
description { "be a valid OpenAPI document" }
|
|
154
|
+
|
|
155
|
+
failure_message do
|
|
156
|
+
next "expected value to be an OpenAPI object" unless comparable?
|
|
157
|
+
|
|
158
|
+
pretty_output = PP.pp(@result.output(:detailed), +"")
|
|
159
|
+
"must valid against OpenAPI specification:\n#{pretty_output}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def comparable?
|
|
163
|
+
actual.is_a?(Skooma::Objects::OpenAPI)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Skooma
|
|
2
|
+
module Validators
|
|
3
|
+
class Double < JSONSkooma::Validators::Base
|
|
4
|
+
self.instance_types = "number"
|
|
5
|
+
|
|
6
|
+
def call(instance)
|
|
7
|
+
return if instance.value.is_a?(::Float)
|
|
8
|
+
|
|
9
|
+
raise JSONSkooma::Validators::FormatError, "must be a valid double"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Skooma
|
|
2
|
+
module Validators
|
|
3
|
+
class Float < JSONSkooma::Validators::Base
|
|
4
|
+
self.instance_types = "number"
|
|
5
|
+
|
|
6
|
+
def call(instance)
|
|
7
|
+
return if instance.value.is_a?(::Float)
|
|
8
|
+
|
|
9
|
+
raise JSONSkooma::Validators::FormatError, "must be a valid float"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Skooma
|
|
2
|
+
module Validators
|
|
3
|
+
class Int32 < JSONSkooma::Validators::Base
|
|
4
|
+
def self.assert?(instance)
|
|
5
|
+
instance.type == "number" && instance == instance.to_i
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(instance)
|
|
9
|
+
return if instance.value.bit_length <= 32
|
|
10
|
+
|
|
11
|
+
raise JSONSkooma::Validators::FormatError, "must be a valid int32"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Skooma
|
|
2
|
+
module Validators
|
|
3
|
+
class Int64 < JSONSkooma::Validators::Base
|
|
4
|
+
def self.assert?(instance)
|
|
5
|
+
instance.type == "number" && instance == instance.to_i
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(instance)
|
|
9
|
+
return if instance.value.bit_length <= 64
|
|
10
|
+
|
|
11
|
+
raise JSONSkooma::Validators::FormatError, "must be a valid int64"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/skooma.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json_skooma"
|
|
4
|
+
require "zeitwerk"
|
|
5
|
+
|
|
6
|
+
require_relative "skooma/inflector"
|
|
7
|
+
|
|
8
|
+
loader = Zeitwerk::Loader.for_gem
|
|
9
|
+
loader.inflector = Skooma::Inflector.new
|
|
10
|
+
loader.setup
|
|
11
|
+
|
|
12
|
+
module Skooma
|
|
13
|
+
DATA_DIR = File.join(__dir__, "..", "data")
|
|
14
|
+
REGISTRY_NAME = "skooma_registry"
|
|
15
|
+
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
JSONSkooma.register_dialect("oas-3.1", Dialects::OAS31)
|
|
19
|
+
JSONSkooma::Formatters.register :skooma, OutputFormat
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
def create_registry
|
|
23
|
+
JSONSkooma.create_registry("2020-12", "oas-3.1", name: REGISTRY_NAME, assert_formats: true)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|