dry_open_api 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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/Custom.md +10 -0
  3. data/.gitignore +12 -0
  4. data/.rubocop.yml +10 -0
  5. data/CHANGELOG.md +0 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +68 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +85 -0
  11. data/Rakefile +7 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/dry_open_api.gemspec +32 -0
  15. data/lib/dry_open_api.rb +49 -0
  16. data/lib/dry_open_api/callback.rb +23 -0
  17. data/lib/dry_open_api/components.rb +54 -0
  18. data/lib/dry_open_api/contact.rb +23 -0
  19. data/lib/dry_open_api/data_types.rb +31 -0
  20. data/lib/dry_open_api/discriminator.rb +40 -0
  21. data/lib/dry_open_api/encoding.rb +25 -0
  22. data/lib/dry_open_api/equatable_as_content.rb +13 -0
  23. data/lib/dry_open_api/example.rb +24 -0
  24. data/lib/dry_open_api/external_documentation.rb +20 -0
  25. data/lib/dry_open_api/header.rb +42 -0
  26. data/lib/dry_open_api/info.rb +38 -0
  27. data/lib/dry_open_api/license.rb +20 -0
  28. data/lib/dry_open_api/link.rb +50 -0
  29. data/lib/dry_open_api/media_type.rb +35 -0
  30. data/lib/dry_open_api/o_auth_flow.rb +25 -0
  31. data/lib/dry_open_api/o_auth_flows.rb +27 -0
  32. data/lib/dry_open_api/operation.rb +60 -0
  33. data/lib/dry_open_api/parameter.rb +66 -0
  34. data/lib/dry_open_api/path_item.rb +42 -0
  35. data/lib/dry_open_api/paths.rb +26 -0
  36. data/lib/dry_open_api/reference.rb +21 -0
  37. data/lib/dry_open_api/request_body.rb +22 -0
  38. data/lib/dry_open_api/response.rb +55 -0
  39. data/lib/dry_open_api/responses.rb +37 -0
  40. data/lib/dry_open_api/schema.rb +107 -0
  41. data/lib/dry_open_api/security_requirement.rb +20 -0
  42. data/lib/dry_open_api/security_schema.rb +35 -0
  43. data/lib/dry_open_api/serializers.rb +7 -0
  44. data/lib/dry_open_api/serializers/yaml_serializer.rb +17 -0
  45. data/lib/dry_open_api/server.rb +21 -0
  46. data/lib/dry_open_api/server_variable.rb +21 -0
  47. data/lib/dry_open_api/specification.rb +44 -0
  48. data/lib/dry_open_api/tag.rb +23 -0
  49. data/lib/dry_open_api/version.rb +3 -0
  50. data/lib/dry_open_api/xml.rb +26 -0
  51. metadata +211 -0
@@ -0,0 +1,27 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#oauth-flows-object
5
+ class OAuthFlows
6
+ prepend EquatableAsContent
7
+ extend Dry::Initializer
8
+
9
+ attr_accessor :implicit, :password, :client_credentials, :authorization_code
10
+
11
+ option :implicit, proc(&:to_s), default: proc { nil }
12
+ option :password, proc(&:to_s), default: proc { nil }
13
+ option :client_credentials, proc(&:to_s), default: proc { nil }
14
+ option :authorization_code, proc(&:to_s), default: proc { nil }
15
+
16
+ def self.load(hash)
17
+ return unless hash
18
+
19
+ new(
20
+ implicit: OAuthFlow.load(hash['implicit']),
21
+ password: OAuthFlow.load(hash['password']),
22
+ client_credentials: OAuthFlow.load(hash['clientCredentials']),
23
+ authorization_code: OAuthFlow.load(hash['authorizationCode']),
24
+ )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ # no corresponding thing in the spec
5
+ class Operation
6
+ prepend EquatableAsContent
7
+ extend Dry::Initializer
8
+
9
+ option :responses
10
+ option :description, default: proc { nil }
11
+ option :summary, default: proc { nil }
12
+ option :operation_id, default: proc { nil }
13
+ option :tags, default: proc { nil }
14
+ option :external_docs, default: proc { nil }
15
+ option :parameters, default: proc { nil }
16
+ option :request_body, default: proc { nil }
17
+ option :callbacks, default: proc { nil }
18
+ option :deprecated, default: proc { nil }
19
+ option :security, default: proc { nil }
20
+ option :servers, default: proc { nil }
21
+
22
+ # rubocop:disable Metrics/MethodLength
23
+ def serializable_hash
24
+ {
25
+ 'description' => description,
26
+ 'responses' => responses.serializable_hash,
27
+ 'tags' => tags&.map(&:to_s),
28
+ 'summary' => summary,
29
+ 'externalDocs' => external_docs&.serializable_hash,
30
+ 'operationId' => operation_id,
31
+ 'parameters' => parameters&.map(&:serializable_hash),
32
+ 'requestBody' => request_body&.serializable_hash,
33
+ 'callbacks' => callbacks&.map { |k, v| [k.to_s, v.serializable_hash] }&.to_h,
34
+ 'deprecated' => deprecated,
35
+ 'security' => security&.map(&:serializable_hash),
36
+ 'servers' => servers&.map(&:server)
37
+ }.compact
38
+ end
39
+
40
+ def self.load(hash)
41
+ return unless hash
42
+
43
+ new(
44
+ responses: Responses.load(hash['responses']),
45
+ tags: hash['tags'],
46
+ summary: hash['summary'],
47
+ description: hash['description'],
48
+ external_docs: ExternalDocumentation.load(hash['externalDocs']),
49
+ operation_id: hash['operationId'],
50
+ parameters: hash['parameters']&.map { |h| Reference.load(h) || Parameter.load(h) },
51
+ request_body: Reference.load(hash['requestBody']) || RequestBody.load(hash['requestBody']),
52
+ callbacks: hash['callbacks']&.map { |k, v| [k, Reference.load(v) || Callback.load(v)] }&.to_h,
53
+ deprecated: hash['deprecated'],
54
+ security: hash['security']&.map { |h| SecurityRequirement.load(h) },
55
+ servers: hash['servers']&.map { |h| Server.load(h) }
56
+ )
57
+ end
58
+ # rubocop:enable Metrics/MethodLength
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ module DryOpenApi
2
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#parameterObject
3
+ class Parameter
4
+ prepend EquatableAsContent
5
+
6
+ attr_accessor :name, :in, :description, :required, :deprecated, :allow_empty_value
7
+
8
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
9
+ def initialize(name:, in:, description: nil, required: nil, deprecated: nil, allow_empty_value: nil, **other_fields_hash)
10
+ self.name = name
11
+ self.in = binding.local_variable_get(:in) # `in` is reserved keyword
12
+ self.required = required
13
+ self.deprecated = deprecated
14
+ self.allow_empty_value = allow_empty_value
15
+ self.other_fields_hash = other_fields_hash.with_indifferent_access
16
+
17
+ other_fields_hash.keys.each do |key|
18
+ define_singleton_method(key) do
19
+ other_fields_hash[key]
20
+ end
21
+ define_singleton_method("#{key}=") do |value|
22
+ other_fields_hash[key] = value
23
+ end
24
+ end
25
+ end
26
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
27
+
28
+ def self.load(hash)
29
+ other_fields_hash = hash.reject { |key|
30
+ key.to_sym.in?([:name, :in, :description, :required, :deprecated, :allow_empty_value])
31
+ }.symbolize_keys.map { |k, v|
32
+ value =
33
+ case k
34
+ when :schema then Schema.load(v)
35
+ end
36
+ [k, value]
37
+ }.to_h
38
+
39
+ new(
40
+ name: hash['name'].to_s,
41
+ in: hash['in'].to_s,
42
+ description: hash['description']&.to_s,
43
+ required: hash['required'],
44
+ deprecated: hash['deprecated'],
45
+ allow_empty_value: hash['allowEmptyValue'],
46
+ **other_fields_hash
47
+ )
48
+ end
49
+
50
+ def serializable_hash
51
+ {
52
+ 'name' => name.to_s,
53
+ 'in' => self.in.to_s,
54
+ 'required' => required,
55
+ 'deprecated' => deprecated,
56
+ 'allow_empty_value' => allow_empty_value
57
+ }.merge(
58
+ other_fields_hash.map { |k, v| [k.to_s, v.serializable_hash] }.to_h
59
+ ).compact
60
+ end
61
+
62
+ private
63
+
64
+ attr_accessor :other_fields_hash
65
+ end
66
+ end
@@ -0,0 +1,42 @@
1
+ module DryOpenApi
2
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#path-item-object
3
+ class PathItem
4
+ prepend EquatableAsContent
5
+
6
+ attr_accessor :ref, :summary, :description, :servers, :parameters, :operations
7
+
8
+ OPERATION_NAMES = %i[get put post delete options head patch trace].freeze
9
+
10
+ def initialize(ref: nil, summary: nil, description: nil, servers: nil, parameters: nil, **operations)
11
+ self.ref = ref
12
+ self.summary = summary
13
+ self.description = description
14
+ self.servers = servers
15
+ self.parameters = parameters
16
+ self.operations = operations.map { |k, v| [k.to_s.underscore, v] }.to_h.with_indifferent_access
17
+ end
18
+
19
+ def serializable_hash
20
+ {
21
+ 'ref' => ref&.to_s,
22
+ 'summary' => summary&.to_s,
23
+ 'description' => description&.to_s,
24
+ 'servers' => servers&.map(&:serializable_hash),
25
+ 'parameters' => parameters&.map(&:serializable_hash)
26
+ }.merge(operations.map { |k, v| [k.to_s.underscore, v.serializable_hash] }.to_h).compact
27
+ end
28
+
29
+ def self.load(hash)
30
+ operations = hash.select { |key| key.to_sym.in?(OPERATION_NAMES) }
31
+
32
+ new(
33
+ ref: hash['$ref']&.to_s,
34
+ summary: hash['summary']&.to_s,
35
+ description: hash['description']&.to_s,
36
+ servers: hash['servers']&.map { |server_hash| Server.load(server_hash) },
37
+ parameters: hash['parameters']&.map { |h| Reference.load(h) || Parameter.load(h) },
38
+ **operations.map { |k, v| [k.to_sym, Operation.load(v)] }.to_h
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ module DryOpenApi
2
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#pathsObject
3
+ class Paths
4
+ extend Forwardable
5
+ prepend EquatableAsContent
6
+
7
+ def initialize(**path_hash)
8
+ self.path_hash = path_hash.with_indifferent_access
9
+ end
10
+
11
+ def_delegators :path_hash, :[], :[]=
12
+
13
+ def self.load(hash)
14
+ hash = hash.map { |k, v| [k.to_sym, PathItem.load(v)] }.to_h
15
+ new(**hash)
16
+ end
17
+
18
+ def serializable_hash
19
+ path_hash.map { |k, v| [k.to_s, v.serializable_hash] }.to_h
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :path_hash
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#referenceObject
5
+ class Reference
6
+ prepend EquatableAsContent
7
+ extend Dry::Initializer
8
+
9
+ option :ref, proc(&:to_s)
10
+
11
+ def serializable_hash
12
+ { '$ref' => ref }
13
+ end
14
+
15
+ def self.load(hash)
16
+ return unless hash && hash['$ref']
17
+
18
+ new(ref: hash['$ref'])
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ class RequestBody
5
+ prepend EquatableAsContent
6
+ extend Dry::Initializer
7
+
8
+ option :content
9
+ option :description, proc(&:to_s), default: proc { nil }
10
+ option :required, default: proc { false }
11
+
12
+ def self.load(hash)
13
+ return unless hash
14
+
15
+ new(
16
+ description: hash['description'],
17
+ content: hash['content'].map { |k, v| [k, MediaType.load(v)] }.to_h,
18
+ required: hash['required'].present?
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ # represents a single response object
5
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject
6
+ class Response
7
+ prepend EquatableAsContent
8
+ extend Dry::Initializer
9
+
10
+ option :description, proc(&:to_s)
11
+
12
+ # rubocop:disable Style/Lambda
13
+ option :headers, ->(headers_param) {
14
+ return if headers_param.nil?
15
+
16
+ headers_param.to_h.with_indifferent_access
17
+ }, default: proc { nil }
18
+
19
+ option :content, ->(content_param) {
20
+ return if content_param.nil?
21
+
22
+ content_param.to_h.with_indifferent_access
23
+ }, default: proc { nil }
24
+
25
+ option :links, ->(links_param) {
26
+ return if links_param.nil?
27
+
28
+ links_param.to_h.with_indifferent_access
29
+ }, default: proc { nil }
30
+
31
+ # rubocop:enable Style/Lambda
32
+
33
+ def serializable_hash
34
+ {
35
+ 'description' => description,
36
+ 'headers' => headers&.map { |k, v| [k.to_s, v.serializable_hash] }&.to_h,
37
+ 'content' => content&.map { |k, v| [k.to_s, v.serializable_hash] }&.to_h,
38
+ 'links' => links&.map { |k, v| [k.to_s, v.serializable_hash] }&.to_h
39
+ }.compact
40
+ end
41
+
42
+ def self.load(hash)
43
+ return unless hash
44
+
45
+ indi_hash = hash.with_indifferent_access
46
+
47
+ new(
48
+ description: indi_hash['description'],
49
+ headers: indi_hash['headers']&.map { |k, v| [k, Header.load(v)] }&.to_h,
50
+ content: indi_hash['content']&.map { |k, v| [k, MediaType.load(v)] }&.to_h,
51
+ links: indi_hash['links']&.map { |k, v| [k, Reference.load(v) || Link.load(v)] }&.to_h
52
+ )
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require 'dry-initializer'
2
+
3
+ module DryOpenApi
4
+ class Responses
5
+ prepend EquatableAsContent
6
+ extend Forwardable
7
+ extend Dry::Initializer
8
+
9
+ attr_accessor :default
10
+
11
+ def initialize(default: nil, **responses_hash)
12
+ self.default = default
13
+ self.responses_hash = responses_hash.with_indifferent_access
14
+ end
15
+
16
+ def_delegators :responses_hash, :[], :[]=
17
+
18
+ def serializable_hash
19
+ {
20
+ 'default' => default&.serializable_hash
21
+ }
22
+ .merge(responses_hash.map { |k, v| [k.to_s, v.serializable_hash] }.to_h)
23
+ .compact
24
+ end
25
+
26
+ def self.load(hash)
27
+ return unless hash
28
+
29
+ hash = hash.map { |k, v| [k.to_s.to_sym, Response.load(v)] }.to_h
30
+ new(**hash)
31
+ end
32
+
33
+ private
34
+
35
+ attr_accessor :responses_hash
36
+ end
37
+ end
@@ -0,0 +1,107 @@
1
+ module DryOpenApi
2
+ class Schema
3
+ prepend EquatableAsContent
4
+
5
+ attr_accessor :nullable, :discriminator, :read_only, :write_only, :xml, :external_docs, :example, :deprecated
6
+
7
+ def initialize(nullable: false, discriminator: nil, read_only: false, write_only: false, xml: nil, external_docs: nil, example: nil, deprecated: false, **other_fields_hash)
8
+ self.nullable = nullable
9
+ self.discriminator = discriminator
10
+ self.read_only = read_only
11
+ self.write_only = write_only
12
+ self.xml = xml
13
+ self.external_docs = external_docs
14
+ self.example = example
15
+ self.deprecated = deprecated
16
+ self.other_fields_hash = other_fields_hash
17
+
18
+ other_fields_hash.keys.each { |name| new_field(name) }
19
+ end
20
+
21
+ def method_missing(mid, *args)
22
+ len = args.length
23
+ if (mname = mid[/.*(?==\z)/m])
24
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) if len != 1
25
+
26
+ new_field(mname)
27
+ other_fields_hash[mname.to_sym] = args[0]
28
+ elsif len.zero?
29
+ if other_fields_hash.key?(mname.to_s)
30
+ new_field(mname)
31
+ other_fields_hash[mname]
32
+ end
33
+ else
34
+ begin
35
+ super
36
+ rescue NoMethodError => e
37
+ e.backtrace.shift
38
+ raise
39
+ end
40
+ end
41
+ end
42
+
43
+ def new_field(name)
44
+ name = name.to_sym
45
+ define_singleton_method(name) { other_fields_hash[name] }
46
+ define_singleton_method("#{name}=") { |value| other_fields_hash[name] = value }
47
+ end
48
+
49
+ def serializable_hash
50
+ converted_other_fields_hash = other_fields_hash.map { |k, v|
51
+ value =
52
+ case k.to_sym
53
+ when :items then v.serializable_hash
54
+ when :properties then v.map { |key, prop_value| [key.to_s, prop_value.serializable_hash] }.to_h
55
+ else
56
+ v.is_a?(Array) ? v.map { |else_value| else_value.try(:serializable_hash) || else_value&.to_s } : v&.to_s
57
+ end
58
+ [k.to_s, value]
59
+ }.to_h
60
+
61
+ {
62
+ 'nullable' => nullable == false ? nil : nullable,
63
+ 'discriminator' => discriminator&.serializable_hash,
64
+ 'readOnly' => read_only == false ? nil : read_only,
65
+ 'writeOnly' => write_only == false ? nil : write_only,
66
+ 'xml' => xml&.serializable_hash,
67
+ 'externalDocs' => external_docs&.serializable_hash,
68
+ 'example' => example,
69
+ 'deprecated' => deprecated == false ? nil : deprecated
70
+ }
71
+ .merge(converted_other_fields_hash)
72
+ .compact
73
+ end
74
+
75
+ def self.load(hash)
76
+ other_fields_hash = hash.reject { |key|
77
+ key.to_sym.in?(%i[nullable discriminator readOnly writeOnly xml externalDocs example deprecated])
78
+ }.map { |k, v|
79
+ loaded_value =
80
+ case k.to_sym
81
+ when :items then Reference.load(v)
82
+ when :properties then v.map { |key, value| [key, Reference.new(value) || Schema.new(value)] }.to_h
83
+ else
84
+ v
85
+ end
86
+
87
+ [k, loaded_value]
88
+ }.to_h
89
+
90
+ new(
91
+ nullable: hash['nullable'].nil? ? false : hash['nullable'],
92
+ discriminator: hash['discriminator'],
93
+ read_only: hash['readOnly'].nil? ? false : hash['readOnly'],
94
+ write_only: hash['writeOnly'].nil? ? false : hash['writeOnly'],
95
+ xml: Xml.load(hash['xml']),
96
+ external_docs: ExternalDocumentation.load(hash['externalDocs']),
97
+ example: Example.load(hash['example']),
98
+ deprecated: hash['deprecated'].nil? ? false : hash['deprecated'],
99
+ **other_fields_hash.symbolize_keys
100
+ )
101
+ end
102
+
103
+ private
104
+
105
+ attr_accessor :other_fields_hash
106
+ end
107
+ end