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.
@@ -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