reynard 0.9.0 → 0.10.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/lib/reynard/context.rb +3 -3
- data/lib/reynard/http/response.rb +1 -1
- data/lib/reynard/model.rb +1 -1
- data/lib/reynard/schema/model_naming.rb +127 -0
- data/lib/reynard/schema.rb +5 -60
- data/lib/reynard/specification/finder.rb +31 -0
- data/lib/reynard/specification/query.rb +14 -0
- data/lib/reynard/specification.rb +12 -0
- data/lib/reynard/version.rb +1 -1
- data/lib/reynard.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d2a2059bfff7777ad1d0b122d83afff99bc6af883a35f8970091fa3197fd541
|
4
|
+
data.tar.gz: 05d9297e7d4d86e43f733f5c385ac2ea93a16b681d4f51e09bea9e20e97a00bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b0ccb3d8e187780cdca33e65588b4da8d835910f247f618e5b67114e58cda744745826ff9e8c223742be55c003ea6df904b18a6fee9bc7e2b7b466c9739f751
|
7
|
+
data.tar.gz: dd5bf8691ae5c72ea7462e313992671ad82125fb0de3228d09d6a26fa367734cd14498de7bc02dda73684b255774b40e5991464c606609bc16517528f75b4219
|
data/lib/reynard/context.rb
CHANGED
@@ -15,7 +15,7 @@ class Reynard
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def base_url(base_url)
|
18
|
-
copy(base_url:
|
18
|
+
copy(base_url:)
|
19
19
|
end
|
20
20
|
|
21
21
|
def operation(operation_name)
|
@@ -44,7 +44,7 @@ class Reynard
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def logger(logger)
|
47
|
-
copy(logger:
|
47
|
+
copy(logger:)
|
48
48
|
end
|
49
49
|
|
50
50
|
def execute
|
@@ -74,7 +74,7 @@ class Reynard
|
|
74
74
|
specification: @specification,
|
75
75
|
inflector: @inflector,
|
76
76
|
request_context: @request_context,
|
77
|
-
http_response:
|
77
|
+
http_response:
|
78
78
|
)
|
79
79
|
end
|
80
80
|
end
|
data/lib/reynard/model.rb
CHANGED
@@ -76,7 +76,7 @@ class Reynard
|
|
76
76
|
property = schema.property_schema(name)
|
77
77
|
return value unless property
|
78
78
|
|
79
|
-
::Reynard::ObjectBuilder.new(schema: property, inflector
|
79
|
+
::Reynard::ObjectBuilder.new(schema: property, inflector:, parsed_body: value).call
|
80
80
|
end
|
81
81
|
|
82
82
|
def self.inflector
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Reynard
|
4
|
+
class Schema
|
5
|
+
# Rummages through the specification to find a suitable model name for a response object.
|
6
|
+
class ModelNaming
|
7
|
+
def initialize(specification:, node:)
|
8
|
+
@specification = specification
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_name
|
13
|
+
model_name = determine_model_name
|
14
|
+
if class_type == :array
|
15
|
+
"#{model_name}Collection"
|
16
|
+
else
|
17
|
+
model_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.title_model_name(model_name)
|
22
|
+
return unless model_name
|
23
|
+
|
24
|
+
model_name
|
25
|
+
.gsub(/[^[:alpha:]]/, ' ')
|
26
|
+
.gsub(/\s{2,}/, ' ')
|
27
|
+
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
28
|
+
.strip
|
29
|
+
end
|
30
|
+
|
31
|
+
# Extracts a model name from a ref when there is a usable value.
|
32
|
+
#
|
33
|
+
# ref_model_name("#/components/schemas/Library") => "Library"
|
34
|
+
def self.ref_model_name(ref)
|
35
|
+
return unless ref
|
36
|
+
|
37
|
+
normalize_ref_model_name(ref.split('/')&.last)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.normalize_ref_model_name(model_name)
|
41
|
+
# 1. Unescape encoded characters to create an UTF-8 string
|
42
|
+
# 2. Remove extensions for regularly used external schema files
|
43
|
+
# 3. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
|
44
|
+
# 4. Camelcase
|
45
|
+
Rack::Utils.unescape_path(model_name)
|
46
|
+
.gsub(/(.yml|.yaml|.json)\Z/, '')
|
47
|
+
.gsub(/[^[:alpha:]]/, ' ')
|
48
|
+
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
49
|
+
.gsub(/\A(.)/) { Regexp.last_match(1).upcase }
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.singularize(name)
|
53
|
+
case name
|
54
|
+
when /ies$/
|
55
|
+
"#{name[0..-4]}y"
|
56
|
+
else
|
57
|
+
name.chomp('s')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def class_type
|
64
|
+
@specification.dig(*@node).key?('items') ? :array : :object
|
65
|
+
end
|
66
|
+
|
67
|
+
def determine_model_name
|
68
|
+
title_model_name || ref_model_name || node_model_name
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns a model name when it was explicitly set using the title property in the specification.
|
72
|
+
def title_model_name
|
73
|
+
title = @specification.dig(*@node, 'title')
|
74
|
+
return unless title
|
75
|
+
|
76
|
+
self.class.title_model_name(title)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a model name based on the schema's $ref value, usually this contains a usable
|
80
|
+
# identifier at the end like /books.yml or /Books.
|
81
|
+
def ref_model_name
|
82
|
+
parent = @specification.dig(*@node[..-2])
|
83
|
+
ref = parent.dig('schema', '$ref') || parent.dig('items', '$ref')
|
84
|
+
return unless ref
|
85
|
+
|
86
|
+
self.class.ref_model_name(ref)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a model name based on the node path to schema in the specification.
|
90
|
+
def node_model_name
|
91
|
+
self.class.title_model_name(node_path_name.capitalize.gsub(/[_-]/, ' '))
|
92
|
+
end
|
93
|
+
|
94
|
+
def node_path_name
|
95
|
+
if node_anyonymous?
|
96
|
+
request_path_model_name
|
97
|
+
elsif @node.last == 'items'
|
98
|
+
# Use the property name as the model name for its items, for example in the case of
|
99
|
+
# schema > properties > birds > items => bird.
|
100
|
+
self.class.singularize(@node.at(-2))
|
101
|
+
else
|
102
|
+
# Usually this means we are dealing with a property's name a not a model, for example in
|
103
|
+
# the case of schema > properties > color => color.
|
104
|
+
@node.last
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true when the node path doesn't have identifyable segments other than the request
|
109
|
+
# path for the resource.
|
110
|
+
#
|
111
|
+
# For example, when the last part of the path looks like this:
|
112
|
+
# get > responses > 200 > content > application|json > schema
|
113
|
+
def node_anyonymous?
|
114
|
+
@node.last == 'schema' || @node.last(2) == %w[schema items]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Finds the first segment starting from the end of the request path that is not a parameter
|
118
|
+
# and transforms that to make a model name.
|
119
|
+
#
|
120
|
+
# For example:
|
121
|
+
# /books/{id} => "book"
|
122
|
+
def request_path_model_name
|
123
|
+
self.class.singularize(@node[1].split('/').reverse.find { |part| !part.start_with?('{') })
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/reynard/schema.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
class Reynard
|
4
4
|
# Holds a references to a schema definition in the specification.
|
5
5
|
class Schema
|
6
|
+
autoload :ModelNaming, 'reynard/schema/model_naming'
|
7
|
+
|
6
8
|
attr_reader :node, :namespace
|
7
9
|
|
8
10
|
def initialize(specification:, node:, namespace: nil)
|
@@ -18,9 +20,7 @@ class Reynard
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def model_name
|
21
|
-
|
22
|
-
|
23
|
-
@model_name = find_model_name
|
23
|
+
@model_name || model_naming.model_name
|
24
24
|
end
|
25
25
|
|
26
26
|
# Returns the schema for items when the current schema is an array.
|
@@ -46,65 +46,10 @@ class Reynard
|
|
46
46
|
)
|
47
47
|
end
|
48
48
|
|
49
|
-
def self.title_model_name(model_name)
|
50
|
-
return unless model_name
|
51
|
-
|
52
|
-
model_name
|
53
|
-
.gsub(/[^[:alpha:]]/, ' ')
|
54
|
-
.gsub(/\s{2,}/, ' ')
|
55
|
-
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
56
|
-
.strip
|
57
|
-
end
|
58
|
-
|
59
|
-
# Extracts a model name from a ref when there is a usable value.
|
60
|
-
#
|
61
|
-
# ref_model_name("#/components/schemas/Library") => "Library"
|
62
|
-
def self.ref_model_name(ref)
|
63
|
-
return unless ref
|
64
|
-
|
65
|
-
normalize_ref_model_name(ref.split('/')&.last)
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.normalize_ref_model_name(model_name)
|
69
|
-
# 1. Unescape encoded characters to create an UTF-8 string
|
70
|
-
# 2. Remove extensions for regularly used external schema files
|
71
|
-
# 3. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
|
72
|
-
# 4. Camelcase
|
73
|
-
Rack::Utils.unescape_path(model_name)
|
74
|
-
.gsub(/(.yml|.yaml|.json)\Z/, '')
|
75
|
-
.gsub(/[^[:alpha:]]/, ' ')
|
76
|
-
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
77
|
-
.gsub(/\A(.)/) { Regexp.last_match(1).upcase }
|
78
|
-
end
|
79
|
-
|
80
49
|
private
|
81
50
|
|
82
|
-
|
83
|
-
|
84
|
-
title_model_name || ref_model_name || node_model_name
|
85
|
-
end
|
86
|
-
|
87
|
-
def title_model_name
|
88
|
-
title = @specification.dig(*node, 'title')
|
89
|
-
return unless title
|
90
|
-
|
91
|
-
self.class.title_model_name(title)
|
92
|
-
end
|
93
|
-
|
94
|
-
def ref_model_name
|
95
|
-
parent = @specification.dig(*node[..-2])
|
96
|
-
ref = parent.dig('schema', '$ref') || parent.dig('items', '$ref')
|
97
|
-
return unless ref
|
98
|
-
|
99
|
-
self.class.ref_model_name(ref)
|
100
|
-
end
|
101
|
-
|
102
|
-
def node_model_name
|
103
|
-
self.class.title_model_name(node_property_name.capitalize.gsub(/[_-]/, ' '))
|
104
|
-
end
|
105
|
-
|
106
|
-
def node_property_name
|
107
|
-
node.last == 'items' ? node.at(-2).chomp('s') : node.last
|
51
|
+
def model_naming
|
52
|
+
ModelNaming.new(specification: @specification, node: @node)
|
108
53
|
end
|
109
54
|
end
|
110
55
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Reynard
|
4
|
+
class Specification
|
5
|
+
# Finds nodes in a specification that match a query.
|
6
|
+
class Finder
|
7
|
+
def initialize(specification:, query:)
|
8
|
+
@specification = specification
|
9
|
+
@query = query
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_each(&)
|
13
|
+
find_into([], &)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def find_into(path, &block)
|
19
|
+
data = @specification.dig(*path)
|
20
|
+
|
21
|
+
yield path if data.respond_to?(:key?) && (data.key?('type') && (@query.type == data['type']))
|
22
|
+
|
23
|
+
return unless data.respond_to?(:each_key)
|
24
|
+
|
25
|
+
data.each_key do |key|
|
26
|
+
find_into([*path, key], &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -5,6 +5,9 @@ require 'rack'
|
|
5
5
|
class Reynard
|
6
6
|
# Wraps the YAML representation of an OpenAPI specification.
|
7
7
|
class Specification
|
8
|
+
autoload :Finder, 'reynard/specification/finder'
|
9
|
+
autoload :Query, 'reynard/specification/query'
|
10
|
+
|
8
11
|
VERBS = %w[get put post delete options head patch trace].freeze
|
9
12
|
|
10
13
|
def initialize(filename:)
|
@@ -17,6 +20,15 @@ class Reynard
|
|
17
20
|
dig_into(@data, @data, path.dup, File.dirname(@filename))
|
18
21
|
end
|
19
22
|
|
23
|
+
# Yields all nodes in the specification matching the specified type.
|
24
|
+
#
|
25
|
+
# specification.find_each(type: 'object') {}
|
26
|
+
#
|
27
|
+
# Please don't use this in a hot paths in production, primarily meant for testing and tooling.
|
28
|
+
def find_each(type:, &block)
|
29
|
+
Finder.new(specification: self, query: Query.new(type:)).find_each(&block)
|
30
|
+
end
|
31
|
+
|
20
32
|
def servers
|
21
33
|
dig('servers').map { |attributes| Server.new(attributes) }
|
22
34
|
end
|
data/lib/reynard/version.rb
CHANGED
data/lib/reynard.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reynard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manfred Stienstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -78,9 +78,12 @@ files:
|
|
78
78
|
- lib/reynard/operation.rb
|
79
79
|
- lib/reynard/request_context.rb
|
80
80
|
- lib/reynard/schema.rb
|
81
|
+
- lib/reynard/schema/model_naming.rb
|
81
82
|
- lib/reynard/serialized_body.rb
|
82
83
|
- lib/reynard/server.rb
|
83
84
|
- lib/reynard/specification.rb
|
85
|
+
- lib/reynard/specification/finder.rb
|
86
|
+
- lib/reynard/specification/query.rb
|
84
87
|
- lib/reynard/template.rb
|
85
88
|
- lib/reynard/version.rb
|
86
89
|
homepage: https://github.com/Manfred/reynard
|
@@ -96,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
99
|
requirements:
|
97
100
|
- - ">"
|
98
101
|
- !ruby/object:Gem::Version
|
99
|
-
version: '3.
|
102
|
+
version: '3.1'
|
100
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
104
|
requirements:
|
102
105
|
- - ">="
|
103
106
|
- !ruby/object:Gem::Version
|
104
107
|
version: '0'
|
105
108
|
requirements: []
|
106
|
-
rubygems_version: 3.5.
|
109
|
+
rubygems_version: 3.5.16
|
107
110
|
signing_key:
|
108
111
|
specification_version: 4
|
109
112
|
summary: Minimal OpenAPI client.
|