apigen 0.0.6 → 0.0.7
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/apigen/formats/json_base.rb +109 -0
- data/lib/apigen/formats/jsonschema.rb +11 -59
- data/lib/apigen/formats/openapi.rb +10 -64
- data/lib/apigen/formats/swagger.rb +10 -60
- data/lib/apigen/migration.rb +55 -0
- data/lib/apigen/model.rb +1 -234
- data/lib/apigen/models/array_type.rb +34 -0
- data/lib/apigen/models/enum_type.rb +25 -0
- data/lib/apigen/models/model.rb +78 -0
- data/lib/apigen/models/object_property.rb +39 -0
- data/lib/apigen/models/object_type.rb +77 -0
- data/lib/apigen/models/oneof_type.rb +33 -0
- data/lib/apigen/models/primary_type.rb +33 -0
- data/lib/apigen/models/reference_type.rb +27 -0
- data/lib/apigen/models/registry.rb +39 -0
- data/lib/apigen/rest.rb +1 -269
- data/lib/apigen/rest/api.rb +73 -0
- data/lib/apigen/rest/endpoint.rb +160 -0
- data/lib/apigen/rest/input.rb +39 -0
- data/lib/apigen/rest/output.rb +45 -0
- metadata +16 -1
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../util'
|
4
|
+
|
5
|
+
module Apigen
|
6
|
+
##
|
7
|
+
# OneofType represents a union type, aka "either or".
|
8
|
+
class OneofType
|
9
|
+
##
|
10
|
+
# The discriminator tells us which property defines the type of the object.
|
11
|
+
#
|
12
|
+
# Setting a discriminator is optional, but recommended.
|
13
|
+
attribute_setter_getter :discriminator
|
14
|
+
attr_reader :mapping
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@discriminator = nil
|
18
|
+
@mapping = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def map(mapping)
|
22
|
+
@mapping = mapping
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate(model_registry)
|
26
|
+
@mapping.each do |key, value|
|
27
|
+
raise 'Mapping keys must be model names (use symbols).' unless key.is_a? Symbol
|
28
|
+
raise 'Mapping values must be strings.' unless value.is_a? String
|
29
|
+
raise "No such model :#{key} for oneof mapping." unless model_registry.models.key? key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Apigen
|
6
|
+
##
|
7
|
+
# PrimaryType represents a primary type such as a string or an integer.
|
8
|
+
class PrimaryType
|
9
|
+
PRIMARY_TYPES = Set.new %i[string int32 bool void]
|
10
|
+
|
11
|
+
def self.primary?(shape)
|
12
|
+
PRIMARY_TYPES.include? shape
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :shape
|
16
|
+
|
17
|
+
def initialize(shape)
|
18
|
+
@shape = shape
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(_model_registry)
|
22
|
+
raise "Unsupported primary type :#{@shape}." unless self.class.primary?(@shape)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
other.is_a?(PrimaryType) && other.shape == shape
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@shape.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Apigen
|
6
|
+
##
|
7
|
+
# ReferenceType represents a reference to a model.
|
8
|
+
class ReferenceType
|
9
|
+
attr_reader :model_name
|
10
|
+
|
11
|
+
def initialize(model_name)
|
12
|
+
@model_name = model_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(model_registry)
|
16
|
+
raise "Model :#{@model_name} is not defined." unless model_registry.models.key? @model_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
other.is_a?(ReferenceType) && other.model_name == model_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@model_name.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './model'
|
4
|
+
|
5
|
+
module Apigen
|
6
|
+
##
|
7
|
+
# ModelRegistry is where all model definitions are stored.
|
8
|
+
class ModelRegistry
|
9
|
+
attr_reader :models
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@models = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def model(name, &block)
|
16
|
+
raise "Model :#{name} is declared twice." if @models.key? name
|
17
|
+
model = Apigen::Model.new name
|
18
|
+
raise 'You must pass a block when calling `model`.' unless block_given?
|
19
|
+
model.instance_eval(&block)
|
20
|
+
@models[model.name] = model
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate
|
24
|
+
@models.each do |_key, model|
|
25
|
+
model.validate self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_type(type)
|
30
|
+
type.validate self
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
@models.map do |key, model|
|
35
|
+
"#{key}: #{model}"
|
36
|
+
end.join "\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/apigen/rest.rb
CHANGED
@@ -1,271 +1,3 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './
|
4
|
-
require_relative './util'
|
5
|
-
|
6
|
-
PATH_PARAMETER_REGEX = /\{(\w+)\}/
|
7
|
-
|
8
|
-
module Apigen
|
9
|
-
##
|
10
|
-
# Rest contains what you need to declare a REST-ish API.
|
11
|
-
module Rest
|
12
|
-
##
|
13
|
-
# Declares an API.
|
14
|
-
def self.api(&block)
|
15
|
-
api = Api.new
|
16
|
-
api.instance_eval(&block)
|
17
|
-
api.validate
|
18
|
-
api
|
19
|
-
end
|
20
|
-
|
21
|
-
##
|
22
|
-
# Api is a self-contained definition of a REST API, includings its endpoints and data types.
|
23
|
-
class Api
|
24
|
-
attr_reader :endpoints
|
25
|
-
attribute_setter_getter :description
|
26
|
-
|
27
|
-
def initialize
|
28
|
-
@description = ''
|
29
|
-
@endpoints = []
|
30
|
-
@model_registry = Apigen::ModelRegistry.new
|
31
|
-
end
|
32
|
-
|
33
|
-
##
|
34
|
-
# Declares a specific endpoint.
|
35
|
-
def endpoint(name, &block)
|
36
|
-
endpoint = Endpoint.new name
|
37
|
-
@endpoints << endpoint
|
38
|
-
endpoint.instance_eval(&block)
|
39
|
-
end
|
40
|
-
|
41
|
-
##
|
42
|
-
# Declares a data model.
|
43
|
-
def model(name, &block)
|
44
|
-
@model_registry.model name, &block
|
45
|
-
end
|
46
|
-
|
47
|
-
def models
|
48
|
-
@model_registry.models
|
49
|
-
end
|
50
|
-
|
51
|
-
def validate
|
52
|
-
@model_registry.validate
|
53
|
-
@endpoints.each do |e|
|
54
|
-
e.validate @model_registry
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def to_s
|
59
|
-
repr = "Endpoints:\n\n"
|
60
|
-
repr += @endpoints.map(&:to_s).join "\n"
|
61
|
-
repr += "\n\nTypes:\n\n"
|
62
|
-
repr += @model_registry.to_s
|
63
|
-
repr
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
##
|
68
|
-
# Endpoint is a definition of a specific endpoint in the API, e.g. /users with GET method.
|
69
|
-
class Endpoint
|
70
|
-
attribute_setter_getter :name
|
71
|
-
attribute_setter_getter :description
|
72
|
-
attr_reader :outputs
|
73
|
-
attr_reader :path_parameters
|
74
|
-
attr_reader :query_parameters
|
75
|
-
|
76
|
-
def initialize(name)
|
77
|
-
@name = name
|
78
|
-
@method = nil
|
79
|
-
@path = nil
|
80
|
-
@path_parameters = Apigen::ObjectType.new
|
81
|
-
@query_parameters = Apigen::ObjectType.new
|
82
|
-
@input = nil
|
83
|
-
@outputs = []
|
84
|
-
@description = nil
|
85
|
-
end
|
86
|
-
|
87
|
-
#
|
88
|
-
# Declares the HTTP method.
|
89
|
-
def method(method = nil)
|
90
|
-
return @method unless method
|
91
|
-
case method
|
92
|
-
when :get, :post, :put, :delete
|
93
|
-
@method = method
|
94
|
-
else
|
95
|
-
raise "Unknown HTTP method :#{method}."
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
#
|
100
|
-
# Declares the endpoint path relative to the host.
|
101
|
-
def path(path = nil, &block)
|
102
|
-
return @path unless path
|
103
|
-
@path = path
|
104
|
-
if PATH_PARAMETER_REGEX.match path
|
105
|
-
set_path_parameters(path, &block)
|
106
|
-
elsif block_given?
|
107
|
-
raise 'A path block was provided but no URL parameter was found.'
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
#
|
112
|
-
# Declares query parameters.
|
113
|
-
def query(&block)
|
114
|
-
raise 'A block must be passed to define query fields.' unless block_given?
|
115
|
-
@query_parameters.instance_eval(&block)
|
116
|
-
end
|
117
|
-
|
118
|
-
##
|
119
|
-
# Declares the input type of an endpoint.
|
120
|
-
def input(&block)
|
121
|
-
return @input unless block_given?
|
122
|
-
@input = Input.new
|
123
|
-
@input.instance_eval(&block)
|
124
|
-
end
|
125
|
-
|
126
|
-
##
|
127
|
-
# Declares the output of an endpoint for a given status code.
|
128
|
-
def output(name, &block)
|
129
|
-
output = Output.new name
|
130
|
-
@outputs << output
|
131
|
-
output.instance_eval(&block)
|
132
|
-
output
|
133
|
-
end
|
134
|
-
|
135
|
-
def validate(model_registry)
|
136
|
-
validate_properties
|
137
|
-
validate_input(model_registry)
|
138
|
-
validate_path_parameters(model_registry)
|
139
|
-
validate_outputs(model_registry)
|
140
|
-
end
|
141
|
-
|
142
|
-
def to_s
|
143
|
-
repr = "#{@name}: #{@input}"
|
144
|
-
@outputs.each do |output|
|
145
|
-
repr += "\n-> #{output}"
|
146
|
-
end
|
147
|
-
repr
|
148
|
-
end
|
149
|
-
|
150
|
-
private
|
151
|
-
|
152
|
-
def set_path_parameters(path, &block)
|
153
|
-
block = {} unless block_given?
|
154
|
-
@path_parameters.instance_eval(&block)
|
155
|
-
expected_parameters = path.scan(PATH_PARAMETER_REGEX).map { |parameter, _| parameter.to_sym }
|
156
|
-
ensure_parameters_all_defined(expected_parameters)
|
157
|
-
end
|
158
|
-
|
159
|
-
def ensure_parameters_all_defined(expected_parameters)
|
160
|
-
expected_parameters.each do |parameter|
|
161
|
-
raise "Path parameter :#{parameter} in path #{@path} is not defined." unless @path_parameters.properties.key? parameter
|
162
|
-
end
|
163
|
-
@path_parameters.properties.each do |parameter, _type|
|
164
|
-
raise "Parameter :#{parameter} does not appear in path #{@path}." unless expected_parameters.include? parameter
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def validate_properties
|
169
|
-
raise 'One of the endpoints is missing a name.' unless @name
|
170
|
-
raise "Use `method :get/post/put/delete` to set an HTTP method for :#{@name}." unless @method
|
171
|
-
raise "Use `path \"/some/path\"` to assign a path to :#{@name}." unless @path
|
172
|
-
end
|
173
|
-
|
174
|
-
def validate_input(model_registry)
|
175
|
-
case @method
|
176
|
-
when :put, :post
|
177
|
-
raise "Use `input { type :typename }` to assign an input type to :#{@name}." unless @input
|
178
|
-
@input.validate(model_registry)
|
179
|
-
when :get
|
180
|
-
raise "Endpoint :#{@name} with method GET cannot accept an input payload." if @input
|
181
|
-
when :delete
|
182
|
-
raise "Endpoint :#{@name} with method DELETE cannot accept an input payload." if @input
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
def validate_path_parameters(model_registry)
|
187
|
-
@path_parameters.validate model_registry
|
188
|
-
end
|
189
|
-
|
190
|
-
def validate_outputs(model_registry)
|
191
|
-
raise "Endpoint :#{@name} does not declare any outputs" if @outputs.empty?
|
192
|
-
@outputs.each do |output|
|
193
|
-
output.validate model_registry
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
##
|
199
|
-
# Input is the request body expected by an API endpoint.
|
200
|
-
class Input
|
201
|
-
attribute_setter_getter :description
|
202
|
-
attribute_setter_getter :example
|
203
|
-
|
204
|
-
def initialize
|
205
|
-
@type = nil
|
206
|
-
@description = nil
|
207
|
-
end
|
208
|
-
|
209
|
-
##
|
210
|
-
# Declares the input type.
|
211
|
-
def type(type = nil, &block)
|
212
|
-
return @type unless type
|
213
|
-
@type = Apigen::Model.type type, &block
|
214
|
-
end
|
215
|
-
|
216
|
-
def validate(model_registry)
|
217
|
-
validate_properties
|
218
|
-
model_registry.check_type @type
|
219
|
-
end
|
220
|
-
|
221
|
-
def to_s
|
222
|
-
@type.to_s
|
223
|
-
end
|
224
|
-
|
225
|
-
private
|
226
|
-
|
227
|
-
def validate_properties
|
228
|
-
raise 'Use `type :typename` to assign a type to the input.' unless @type
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
##
|
233
|
-
# Output is the response type associated with a specific status code for an API endpoint.
|
234
|
-
class Output
|
235
|
-
attribute_setter_getter :status
|
236
|
-
attribute_setter_getter :description
|
237
|
-
attribute_setter_getter :example
|
238
|
-
|
239
|
-
def initialize(name)
|
240
|
-
@name = name
|
241
|
-
@status = nil
|
242
|
-
@type = nil
|
243
|
-
@description = nil
|
244
|
-
end
|
245
|
-
|
246
|
-
##
|
247
|
-
# Declares the output type.
|
248
|
-
def type(type = nil, &block)
|
249
|
-
return @type unless type
|
250
|
-
@type = Apigen::Model.type type, &block
|
251
|
-
end
|
252
|
-
|
253
|
-
def validate(model_registry)
|
254
|
-
validate_properties
|
255
|
-
model_registry.check_type @type
|
256
|
-
end
|
257
|
-
|
258
|
-
def to_s
|
259
|
-
"#{@name} #{@status} #{@type}"
|
260
|
-
end
|
261
|
-
|
262
|
-
private
|
263
|
-
|
264
|
-
def validate_properties
|
265
|
-
raise 'One of the outputs is missing a name.' unless @name
|
266
|
-
raise "Use `status [code]` to assign a status code to :#{@name}." unless @status
|
267
|
-
raise "Use `type :typename` to assign a type to :#{@name}." unless @type
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
3
|
+
require_relative './rest/api'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../util'
|
4
|
+
require_relative './endpoint'
|
5
|
+
require_relative '../models/registry'
|
6
|
+
|
7
|
+
module Apigen
|
8
|
+
##
|
9
|
+
# Rest contains what you need to declare a REST-ish API.
|
10
|
+
module Rest
|
11
|
+
##
|
12
|
+
# Declares an API.
|
13
|
+
def self.api(&block)
|
14
|
+
api = Api.new
|
15
|
+
raise 'You must a block when calling `api`.' unless block_given?
|
16
|
+
api.instance_eval(&block)
|
17
|
+
api.validate
|
18
|
+
api
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Api is a self-contained definition of a REST API, includings its endpoints and data types.
|
23
|
+
class Api
|
24
|
+
attr_reader :endpoints
|
25
|
+
attribute_setter_getter :description
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@description = ''
|
29
|
+
@endpoints = []
|
30
|
+
@model_registry = Apigen::ModelRegistry.new
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Declares a specific endpoint.
|
35
|
+
def endpoint(name, &block)
|
36
|
+
raise "Endpoint :#{name} is declared twice." if @endpoints.find { |e| e.name == name }
|
37
|
+
endpoint = Endpoint.new name
|
38
|
+
@endpoints << endpoint
|
39
|
+
raise 'You must pass a block when calling `endpoint`.' unless block_given?
|
40
|
+
endpoint.instance_eval(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Declares a data model.
|
45
|
+
def model(name, &block)
|
46
|
+
@model_registry.model name, &block
|
47
|
+
end
|
48
|
+
|
49
|
+
def models
|
50
|
+
@model_registry.models
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate
|
54
|
+
@model_registry.validate
|
55
|
+
@endpoints.each do |e|
|
56
|
+
e.validate @model_registry
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def migrate(*migration_classes)
|
61
|
+
migration_classes.each { |klass| klass.new(self).up }
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
repr = "Endpoints:\n\n"
|
66
|
+
repr += @endpoints.map(&:to_s).join "\n"
|
67
|
+
repr += "\nTypes:\n\n"
|
68
|
+
repr += @model_registry.to_s
|
69
|
+
repr
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|