openapi3_parser 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/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +9 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/LICENCE +19 -0
- data/README.md +32 -0
- data/Rakefile +10 -0
- data/TODO.md +30 -0
- data/lib/openapi3_parser.rb +39 -0
- data/lib/openapi3_parser/context.rb +54 -0
- data/lib/openapi3_parser/document.rb +48 -0
- data/lib/openapi3_parser/error.rb +5 -0
- data/lib/openapi3_parser/fields/map.rb +83 -0
- data/lib/openapi3_parser/node.rb +115 -0
- data/lib/openapi3_parser/node/map.rb +24 -0
- data/lib/openapi3_parser/node/object.rb +28 -0
- data/lib/openapi3_parser/node_factories/array.rb +105 -0
- data/lib/openapi3_parser/node_factories/callback.rb +26 -0
- data/lib/openapi3_parser/node_factories/components.rb +83 -0
- data/lib/openapi3_parser/node_factories/contact.rb +24 -0
- data/lib/openapi3_parser/node_factories/discriminator.rb +31 -0
- data/lib/openapi3_parser/node_factories/encoding.rb +34 -0
- data/lib/openapi3_parser/node_factories/example.rb +25 -0
- data/lib/openapi3_parser/node_factories/external_documentation.rb +23 -0
- data/lib/openapi3_parser/node_factories/header.rb +36 -0
- data/lib/openapi3_parser/node_factories/info.rb +28 -0
- data/lib/openapi3_parser/node_factories/license.rb +22 -0
- data/lib/openapi3_parser/node_factories/link.rb +40 -0
- data/lib/openapi3_parser/node_factories/map.rb +110 -0
- data/lib/openapi3_parser/node_factories/media_type.rb +44 -0
- data/lib/openapi3_parser/node_factories/oauth_flow.rb +24 -0
- data/lib/openapi3_parser/node_factories/oauth_flows.rb +31 -0
- data/lib/openapi3_parser/node_factories/openapi.rb +50 -0
- data/lib/openapi3_parser/node_factories/operation.rb +76 -0
- data/lib/openapi3_parser/node_factories/parameter.rb +43 -0
- data/lib/openapi3_parser/node_factories/parameter/parameter_like.rb +37 -0
- data/lib/openapi3_parser/node_factories/path_item.rb +75 -0
- data/lib/openapi3_parser/node_factories/paths.rb +32 -0
- data/lib/openapi3_parser/node_factories/reference.rb +33 -0
- data/lib/openapi3_parser/node_factories/request_body.rb +31 -0
- data/lib/openapi3_parser/node_factories/response.rb +45 -0
- data/lib/openapi3_parser/node_factories/responses.rb +32 -0
- data/lib/openapi3_parser/node_factories/schema.rb +106 -0
- data/lib/openapi3_parser/node_factories/security_requirement.rb +25 -0
- data/lib/openapi3_parser/node_factories/security_scheme.rb +35 -0
- data/lib/openapi3_parser/node_factories/server.rb +32 -0
- data/lib/openapi3_parser/node_factories/server_variable.rb +28 -0
- data/lib/openapi3_parser/node_factories/tag.rb +24 -0
- data/lib/openapi3_parser/node_factories/xml.rb +25 -0
- data/lib/openapi3_parser/node_factory.rb +126 -0
- data/lib/openapi3_parser/node_factory/field_config.rb +88 -0
- data/lib/openapi3_parser/node_factory/map.rb +39 -0
- data/lib/openapi3_parser/node_factory/object.rb +80 -0
- data/lib/openapi3_parser/node_factory/object/node_builder.rb +85 -0
- data/lib/openapi3_parser/node_factory/object/validator.rb +102 -0
- data/lib/openapi3_parser/node_factory/optional_reference.rb +23 -0
- data/lib/openapi3_parser/nodes/array.rb +26 -0
- data/lib/openapi3_parser/nodes/callback.rb +11 -0
- data/lib/openapi3_parser/nodes/components.rb +47 -0
- data/lib/openapi3_parser/nodes/contact.rb +23 -0
- data/lib/openapi3_parser/nodes/discriminator.rb +19 -0
- data/lib/openapi3_parser/nodes/encoding.rb +31 -0
- data/lib/openapi3_parser/nodes/example.rb +27 -0
- data/lib/openapi3_parser/nodes/external_documentation.rb +19 -0
- data/lib/openapi3_parser/nodes/header.rb +13 -0
- data/lib/openapi3_parser/nodes/info.rb +35 -0
- data/lib/openapi3_parser/nodes/license.rb +19 -0
- data/lib/openapi3_parser/nodes/link.rb +35 -0
- data/lib/openapi3_parser/nodes/map.rb +11 -0
- data/lib/openapi3_parser/nodes/media_type.rb +27 -0
- data/lib/openapi3_parser/nodes/oauth_flow.rb +27 -0
- data/lib/openapi3_parser/nodes/oauth_flows.rb +27 -0
- data/lib/openapi3_parser/nodes/openapi.rb +44 -0
- data/lib/openapi3_parser/nodes/operation.rb +59 -0
- data/lib/openapi3_parser/nodes/parameter.rb +21 -0
- data/lib/openapi3_parser/nodes/parameter/parameter_like.rb +53 -0
- data/lib/openapi3_parser/nodes/path_item.rb +59 -0
- data/lib/openapi3_parser/nodes/paths.rb +11 -0
- data/lib/openapi3_parser/nodes/request_body.rb +23 -0
- data/lib/openapi3_parser/nodes/response.rb +27 -0
- data/lib/openapi3_parser/nodes/responses.rb +15 -0
- data/lib/openapi3_parser/nodes/schema.rb +159 -0
- data/lib/openapi3_parser/nodes/security_requirement.rb +11 -0
- data/lib/openapi3_parser/nodes/security_scheme.rb +44 -0
- data/lib/openapi3_parser/nodes/server.rb +25 -0
- data/lib/openapi3_parser/nodes/server_variable.rb +23 -0
- data/lib/openapi3_parser/nodes/tag.rb +23 -0
- data/lib/openapi3_parser/nodes/xml.rb +31 -0
- data/lib/openapi3_parser/validation/error.rb +14 -0
- data/lib/openapi3_parser/validation/error_collection.rb +36 -0
- data/lib/openapi3_parser/version.rb +5 -0
- data/openapi3_parser.gemspec +28 -0
- metadata +208 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 3c7e2585cb3bbdbda459519eb7e264dfd89f5745
|
|
4
|
+
data.tar.gz: 4fdbb4b547e626e1b0ba5f502b93107638af3655
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 498d2043a338970c66ef9d14590adb34ec2f7b65e751985f8d12a8c9506d63e09a9840e7521b00a953000af1e286917147fb4507be29f68a5c047aa9f124f7d8
|
|
7
|
+
data.tar.gz: 7fb4d6b168b514fa01056fa5be0026551ad02e0e47506e99516b14b290e450c0940031e82fc38bdc0f4c5d137d98d3a621f28d42bd6e5923f335a7c31255796e
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/Gemfile.lock
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENCE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2017 Kevin Dew
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# OpenAPI 3 Parser
|
|
2
|
+
|
|
3
|
+
[](https://travis-ci.org/kevindew/openapi_parser)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
This is a parser/validator for [Open API 3][openapi-3] built in Ruby.
|
|
7
|
+
|
|
8
|
+
Example usage:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
require "openapi3_parser"
|
|
12
|
+
|
|
13
|
+
document = Openapi3Parser.load_file("path/to/example.yaml")
|
|
14
|
+
|
|
15
|
+
# check whether document is valid
|
|
16
|
+
document.valid?
|
|
17
|
+
|
|
18
|
+
# traverse document
|
|
19
|
+
document.paths["/"]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
[openapi-3]: https://github.com/OAI/OpenAPI-Specification
|
|
23
|
+
|
|
24
|
+
## Status
|
|
25
|
+
|
|
26
|
+
This is currently a work in progress and will remain so until it reaches 1.0.
|
|
27
|
+
|
|
28
|
+
See [TODO](TODO.md) for details of the roadmap there.
|
|
29
|
+
|
|
30
|
+
## Licence
|
|
31
|
+
|
|
32
|
+
[MIT License](LICENCE)
|
data/Rakefile
ADDED
data/TODO.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Todo
|
|
2
|
+
|
|
3
|
+
These are the steps defined to reach 1.0. Assistance is very welcome.
|
|
4
|
+
|
|
5
|
+
- [ ] Handle mutually exclusive fields
|
|
6
|
+
- [ ] Refactor the various NodeFactory modules to be a less confusing
|
|
7
|
+
hierachical structure. Consider having factories subclass instead of use
|
|
8
|
+
mixin
|
|
9
|
+
- [ ] Decouple Document class for the source file. Consider a source file class
|
|
10
|
+
instead
|
|
11
|
+
- [ ] Validate that a reference creates the type of node that is expected in
|
|
12
|
+
a context
|
|
13
|
+
- [ ] Allow opening of references from additional files
|
|
14
|
+
- [ ] Allow opening of openapi documents by URL
|
|
15
|
+
- [ ] Support references by URL, consider option to limit behaviour
|
|
16
|
+
- [ ] Support converting CommonMark to HTML
|
|
17
|
+
- [ ] Reach parity with OpenAPI specification for validation
|
|
18
|
+
- [ ] Consider a lenient mode for a document to only have to comply with type
|
|
19
|
+
based validation
|
|
20
|
+
- [ ] Improve test coverage
|
|
21
|
+
- [ ] Publish documentation of the interface through the structure
|
|
22
|
+
- [ ] Consider a resolved context class for representing context with a node
|
|
23
|
+
that can handle scenarios where a node is represented by both a reference
|
|
24
|
+
and resolved context
|
|
25
|
+
- [ ] Create error classes for various scenarios
|
|
26
|
+
- [ ] Associate/resolve operation id / operation references
|
|
27
|
+
- [ ] Do something to model expressions
|
|
28
|
+
- [ ] Improve the modelling of namespace
|
|
29
|
+
- [ ] Set up nicer string representations of key classes to help them be
|
|
30
|
+
debugged
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openapi3_parser/error"
|
|
4
|
+
require "openapi3_parser/document"
|
|
5
|
+
|
|
6
|
+
require "yaml"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
module Openapi3Parser
|
|
10
|
+
def self.load(input)
|
|
11
|
+
# working_directory ||= if input.respond_to?(:read)
|
|
12
|
+
# File.dirname(input)
|
|
13
|
+
# else
|
|
14
|
+
# Dir.pwd
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
Document.new(parse_input(input))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.load_file(path)
|
|
21
|
+
file = File.open(path)
|
|
22
|
+
load(file)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.parse_input(input)
|
|
26
|
+
return input if input.respond_to?(:keys)
|
|
27
|
+
|
|
28
|
+
extension = input.respond_to?(:extname) ? input.extname : nil
|
|
29
|
+
contents = input.respond_to?(:read) ? input.read : input
|
|
30
|
+
|
|
31
|
+
if extension == ".json" || contents.strip[0] == "{"
|
|
32
|
+
JSON.parse(contents)
|
|
33
|
+
else
|
|
34
|
+
YAML.safe_load(contents, [], [], true)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private_class_method :parse_input
|
|
39
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openapi3Parser
|
|
4
|
+
class Context
|
|
5
|
+
attr_reader :input, :namespace, :document, :parent
|
|
6
|
+
|
|
7
|
+
def initialize(input:, namespace: [], document:, parent: nil)
|
|
8
|
+
@input = input
|
|
9
|
+
@namespace = namespace.freeze
|
|
10
|
+
@document = document
|
|
11
|
+
@parent = parent
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.root(input, document)
|
|
15
|
+
new(input: input, document: document)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def stringify_namespace
|
|
19
|
+
return "root" if namespace.empty?
|
|
20
|
+
namespace
|
|
21
|
+
.map { |i| i.include?("/") ? %("#{i}") : i }
|
|
22
|
+
.join("/")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def next_namespace(segment, next_input = nil)
|
|
26
|
+
next_input ||= input[segment]
|
|
27
|
+
self.class.new(
|
|
28
|
+
input: next_input,
|
|
29
|
+
namespace: namespace + [segment],
|
|
30
|
+
document: document,
|
|
31
|
+
parent: self
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_reference
|
|
36
|
+
document.resolve_reference(input["$ref"]) do |resolved_input, namespace|
|
|
37
|
+
# @TODO track reference for cyclic depenendies
|
|
38
|
+
next_context = resolved_reference(resolved_input, namespace)
|
|
39
|
+
yield(next_context)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def resolved_reference(input, namespace)
|
|
46
|
+
self.class.new(
|
|
47
|
+
input: input,
|
|
48
|
+
namespace: namespace,
|
|
49
|
+
document: document,
|
|
50
|
+
parent: parent
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openapi3_parser/context"
|
|
4
|
+
require "openapi3_parser/error"
|
|
5
|
+
require "openapi3_parser/node_factories/openapi"
|
|
6
|
+
|
|
7
|
+
require "forwardable"
|
|
8
|
+
|
|
9
|
+
module Openapi3Parser
|
|
10
|
+
class Document
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
13
|
+
attr_reader :input
|
|
14
|
+
|
|
15
|
+
def_delegators :factory, :valid?, :errors
|
|
16
|
+
def_delegators :root, :openapi, :info, :servers, :paths, :components,
|
|
17
|
+
:security, :tags, :external_docs, :extension, :[], :each
|
|
18
|
+
|
|
19
|
+
def initialize(input)
|
|
20
|
+
@input = input
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def root
|
|
24
|
+
factory.node
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def resolve_reference(reference)
|
|
28
|
+
if reference[0..1] != "#/"
|
|
29
|
+
raise Error, "Only anchor references are currently supported"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
parts = reference.split("/").drop(1).map do |field|
|
|
33
|
+
CGI.unescape(field.gsub("+", "%20"))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result = input.dig(*parts)
|
|
37
|
+
raise Error, "Could not resolve reference #{reference}" unless result
|
|
38
|
+
|
|
39
|
+
yield(result, parts)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def factory
|
|
45
|
+
@factory ||= NodeFactories::Openapi.new(Context.root(input, self))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openapi3_parser/error"
|
|
4
|
+
|
|
5
|
+
module Openapi3Parser
|
|
6
|
+
module Fields
|
|
7
|
+
class Map
|
|
8
|
+
private_class_method :new
|
|
9
|
+
|
|
10
|
+
def initialize(input, context, value_type, key_format)
|
|
11
|
+
@input = input
|
|
12
|
+
@context = context
|
|
13
|
+
@value_type = value_type
|
|
14
|
+
@key_format = key_format
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.call(
|
|
18
|
+
input,
|
|
19
|
+
context,
|
|
20
|
+
value_type: Hash,
|
|
21
|
+
key_format: nil,
|
|
22
|
+
&block
|
|
23
|
+
)
|
|
24
|
+
new(input, context, value_type, key_format).call(&block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.reference_input(
|
|
28
|
+
input,
|
|
29
|
+
context,
|
|
30
|
+
value_type: Hash,
|
|
31
|
+
key_format: nil,
|
|
32
|
+
&block
|
|
33
|
+
)
|
|
34
|
+
call(
|
|
35
|
+
input, context, value_type: value_type, key_format: key_format
|
|
36
|
+
) do |field_input, field_context|
|
|
37
|
+
field_context.possible_reference(field_input, &block)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call(&block)
|
|
42
|
+
validate_keys
|
|
43
|
+
validate_values
|
|
44
|
+
|
|
45
|
+
input.each_with_object({}) do |(key, value), memo|
|
|
46
|
+
memo[key] = if block
|
|
47
|
+
yield(value, context.next_namespace(key), key)
|
|
48
|
+
else
|
|
49
|
+
value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
attr_reader :input, :context, :value_type, :key_format
|
|
57
|
+
|
|
58
|
+
def validate_keys
|
|
59
|
+
return unless key_format
|
|
60
|
+
invalid_keys = input.keys.reject { |key| key =~ key_format }
|
|
61
|
+
return if invalid_keys.empty?
|
|
62
|
+
|
|
63
|
+
raise Openapi3Parser::Error, "Invalid field names for "\
|
|
64
|
+
"#{context.stringify_namespace}: #{invalid_keys.join(', ')}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_values
|
|
68
|
+
return unless value_type
|
|
69
|
+
invalid = input.reject do |key, value|
|
|
70
|
+
if value_type.is_a?(Proc)
|
|
71
|
+
value.call(value, key)
|
|
72
|
+
else
|
|
73
|
+
value.is_a?(value_type)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
return if invalid.empty?
|
|
77
|
+
|
|
78
|
+
raise Openapi3Parser::Error, "Unexpected type for "\
|
|
79
|
+
"#{context.stringify_namespace}: #{invalid.keys.join(', ')}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openapi3_parser/node/field_config"
|
|
4
|
+
|
|
5
|
+
module Openapi3Parser
|
|
6
|
+
module Node
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def field(name, **options)
|
|
9
|
+
@field_configs ||= {}
|
|
10
|
+
@field_configs[name] = FieldConfig.new(options)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def field_configs
|
|
14
|
+
@field_configs || {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def allow_extensions
|
|
18
|
+
@allow_extensions = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def disallow_extensions
|
|
22
|
+
@allow_extensions = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def allowed_extensions?
|
|
26
|
+
@allow_extensions == true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.included(base)
|
|
31
|
+
base.extend(ClassMethods)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
EXTENSION_REGEX = /^x-(.*)/
|
|
35
|
+
|
|
36
|
+
attr_reader :input, :context, :fields
|
|
37
|
+
|
|
38
|
+
def initialize(input, context)
|
|
39
|
+
@input = input
|
|
40
|
+
@context = context
|
|
41
|
+
@fields = build_fields(input)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def [](value)
|
|
45
|
+
fields[value]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def extension(value)
|
|
49
|
+
fields["x-#{value}"]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def build_fields(input)
|
|
55
|
+
check_for_unexpected_fields(input)
|
|
56
|
+
create_fields(input)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def check_for_unexpected_fields(input)
|
|
60
|
+
allowed_fields = field_configs.keys
|
|
61
|
+
remaining_fields = input.keys - allowed_fields
|
|
62
|
+
return if remaining_fields.empty?
|
|
63
|
+
|
|
64
|
+
if allowed_extensions?
|
|
65
|
+
remaining_fields.reject! { |key| key =~ EXTENSION_REGEX }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
return if remaining_fields.empty?
|
|
69
|
+
raise Error,
|
|
70
|
+
"Unexpected attributes for #{context.stringify_namespace}: "\
|
|
71
|
+
"#{remaining_fields.join(', ')}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def create_fields(input)
|
|
75
|
+
check_required(input)
|
|
76
|
+
check_types(input)
|
|
77
|
+
fields = field_configs.each_with_object({}) do |(field, config), memo|
|
|
78
|
+
next_context = context.next_namespace(field)
|
|
79
|
+
memo[field] = config.build(input[field], self, next_context)
|
|
80
|
+
end
|
|
81
|
+
extensions = input.select { |(k, _)| k =~ EXTENSION_REGEX }
|
|
82
|
+
fields.merge(extensions)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check_required(input)
|
|
86
|
+
missing = field_configs.reject do |field, config|
|
|
87
|
+
config.valid_presence?(input[field])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
return if missing.empty?
|
|
91
|
+
raise Error,
|
|
92
|
+
"Missing required fields for #{context.stringify_namespace}: "\
|
|
93
|
+
"#{missing.keys}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def check_types(input)
|
|
97
|
+
invalid = field_configs.reject do |field, config|
|
|
98
|
+
config.valid_input_type?(input[field], self)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return if invalid.empty?
|
|
102
|
+
raise Error,
|
|
103
|
+
"Invalid fields for #{context.stringify_namespace}: "\
|
|
104
|
+
"#{invalid.keys}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def allowed_extensions?
|
|
108
|
+
self.class.allowed_extensions?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def field_configs
|
|
112
|
+
self.class.field_configs || {}
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|