apigen 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,271 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './model'
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