apigen 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d490568fbf42afb3f2c5ee580739f02bf13403444c0c852004d44b9dc6a1c209
4
- data.tar.gz: be1468d137a7efcee80ab31f496a7ffe74bd7eb09cdcf7514e8d569a79789d15
3
+ metadata.gz: d6432dfded34004dd760e36e95f01fba2714a01184cee2f5fa2d3de2f77c56e1
4
+ data.tar.gz: 4f5548567d427df026ca5ccdd73758ad2fde6115bc52c48785c32bd2527293c8
5
5
  SHA512:
6
- metadata.gz: 4f55e4d77ef95e85938b51306fa0f0cc09ec5d6371eff815d50033760bed8a792d525bd2e7439dbec4eb324022ca88c176cd30b9674db2920d5bfd29a4e3ce4e
7
- data.tar.gz: 6b2fc1dfc6ab532aa69ceb4d0c6d2c6143d7604bfd0002fe1317209ef245842e12fc620a508ea72edc69049307f43a455048f26836f812c327c354e38a32de42
6
+ metadata.gz: ef5b8bad5d1568dbdcca3737eae32ca85bf2be0e9286dc4ab55a7b6a9157b98f8d18a1be570365387e0196ab05aeb34dfab58578b122517f06357b27e0af8cdf
7
+ data.tar.gz: 067d66a73b3353443fc1b37c15b0912f14ae7a8be3ef081ef2ff8be7cf466f9703267415f4e4d88bc14355ce64ef43d4d487a42f35fc886f3ab75cee671fb488
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apigen
4
+ module Formats
5
+ ##
6
+ # JsonBase contains the common logic for API declaration formats based on JSON Schema,
7
+ # such as JSON Schema itself, Swagger and OpenAPI.
8
+ module JsonBase
9
+ private
10
+
11
+ def definitions(api)
12
+ hash = {}
13
+ api.models.each do |key, model|
14
+ hash[key.to_s] = schema(api, model.type, model.description, model.example)
15
+ end
16
+ hash
17
+ end
18
+
19
+ def schema(api, type, description = nil, example = nil)
20
+ schema = schema_without_description(api, type)
21
+ schema['description'] = description unless description.nil?
22
+ schema['example'] = example unless example.nil?
23
+ schema
24
+ end
25
+
26
+ def schema_without_description(api, type)
27
+ case type
28
+ when Apigen::ObjectType
29
+ object_schema(api, type)
30
+ when Apigen::ArrayType
31
+ array_schema(api, type)
32
+ when Apigen::OneofType
33
+ oneof_schema(type)
34
+ when Apigen::EnumType
35
+ enum_schema(type)
36
+ when Apigen::PrimaryType
37
+ primary_schema(type)
38
+ when Apigen::ReferenceType
39
+ reference_schema(type)
40
+ else
41
+ raise "Unsupported type: #{type}."
42
+ end
43
+ end
44
+
45
+ def object_schema(api, object_type)
46
+ {
47
+ 'type' => 'object',
48
+ 'properties' => object_type.properties.map { |name, property| object_property(api, name, property) }.to_h,
49
+ 'required' => object_type.properties.select { |_name, property| property.required? }.map { |name, _property| name.to_s }
50
+ }
51
+ end
52
+
53
+ def object_property(api, name, property)
54
+ [name.to_s, schema(api, property.type, property.description, property.example)]
55
+ end
56
+
57
+ def array_schema(api, array_type)
58
+ {
59
+ 'type' => 'array',
60
+ 'items' => schema(api, array_type.type)
61
+ }
62
+ end
63
+
64
+ def oneof_schema(oneof_type)
65
+ schema = {
66
+ 'oneOf' => oneof_type.mapping.keys.map { |model_name| { '$ref' => model_ref(model_name) } }
67
+ }
68
+ if supports_discriminator? && oneof_type.discriminator
69
+ schema['discriminator'] = {
70
+ 'propertyName' => oneof_type.discriminator.to_s,
71
+ 'mapping' => oneof_type.mapping.map { |model_name, disc_value| [disc_value, model_ref(model_name)] }.to_h
72
+ }
73
+ end
74
+ schema
75
+ end
76
+
77
+ def enum_schema(enum_type)
78
+ {
79
+ 'type' => 'string',
80
+ 'enum' => enum_type.values
81
+ }
82
+ end
83
+
84
+ def primary_schema(primary_type)
85
+ case primary_type.shape
86
+ when :string
87
+ {
88
+ 'type' => 'string'
89
+ }
90
+ when :int32
91
+ {
92
+ 'type' => 'integer',
93
+ 'format' => 'int32'
94
+ }
95
+ when :bool
96
+ {
97
+ 'type' => 'boolean'
98
+ }
99
+ else
100
+ raise "Unsupported primary type :#{primary_type.shape}."
101
+ end
102
+ end
103
+
104
+ def reference_schema(reference_type)
105
+ { '$ref' => model_ref(reference_type.model_name) }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require_relative './json_base'
4
5
 
5
6
  module Apigen
6
7
  module Formats
@@ -9,72 +10,23 @@ module Apigen
9
10
  # JSON Schema Draft 7 generator.
10
11
  module Draft7
11
12
  class << self
12
- def generate(api)
13
- JSON.pretty_generate definitions(api)
14
- end
15
-
16
- private
13
+ include Apigen::Formats::JsonBase
17
14
 
18
- def definitions(api)
19
- {
15
+ def generate(api)
16
+ JSON.pretty_generate(
20
17
  '$schema' => 'http://json-schema.org/draft-07/schema#',
21
- 'definitions' => api.models.map { |key, model| [key.to_s, schema(api, model.type, model.description, model.example)] }.to_h
22
- }
18
+ 'definitions' => definitions(api)
19
+ )
23
20
  end
24
21
 
25
- def schema(api, type, description = nil, example = nil)
26
- schema = schema_without_description(api, type)
27
- schema['description'] = description unless description.nil?
28
- schema['example'] = example unless example.nil?
29
- schema
30
- end
31
-
32
- def schema_without_description(api, type)
33
- case type
34
- when Apigen::ObjectType
35
- object_schema(api, type)
36
- when Apigen::ArrayType
37
- array_schema(api, type)
38
- when Apigen::OptionalType
39
- raise 'OptionalType fields are only supported within object types.'
40
- when :string
41
- {
42
- 'type' => 'string'
43
- }
44
- when :int32
45
- {
46
- 'type' => 'integer',
47
- 'format' => 'int32'
48
- }
49
- when :bool
50
- {
51
- 'type' => 'boolean'
52
- }
53
- else
54
- return { '$ref' => "#/definitions/#{type}" } if api.models.key? type
55
- raise "Unsupported type: #{type}."
56
- end
57
- end
58
-
59
- def object_schema(api, object_type)
60
- {
61
- 'type' => 'object',
62
- 'properties' => object_type.properties.map { |name, property| object_property(api, name, property) }.to_h,
63
- 'required' => object_type.properties.reject { |_name, property| property.type.is_a? Apigen::OptionalType }.map { |name, _property| name.to_s }
64
- }
65
- end
22
+ private
66
23
 
67
- def object_property(api, name, property)
68
- # A property is never optional, because we specify which are required on the schema itself.
69
- actual_type = property.type.is_a?(Apigen::OptionalType) ? property.type.type : property.type
70
- [name.to_s, schema(api, actual_type, property.description, property.example)]
24
+ def model_ref(type)
25
+ "#/definitions/#{type}"
71
26
  end
72
27
 
73
- def array_schema(api, array_type)
74
- {
75
- 'type' => 'array',
76
- 'items' => schema(api, array_type.type)
77
- }
28
+ def supports_discriminator?
29
+ false
78
30
  end
79
31
  end
80
32
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require_relative './json_base'
4
5
 
5
6
  module Apigen
6
7
  module Formats
@@ -9,6 +10,8 @@ module Apigen
9
10
  # OpenAPI 3 generator.
10
11
  module V3
11
12
  class << self
13
+ include Apigen::Formats::JsonBase
14
+
12
15
  def generate(api)
13
16
  # TODO: Allow overriding any of the hardcoded elements.
14
17
  {
@@ -72,13 +75,11 @@ module Apigen
72
75
  end
73
76
 
74
77
  def query_parameter(api, name, property)
75
- optional = property.type.is_a?(Apigen::OptionalType)
76
- actual_type = optional ? property.type.type : property.type
77
78
  parameter = {
78
79
  'in' => 'query',
79
80
  'name' => name.to_s,
80
- 'required' => !optional,
81
- 'schema' => schema(api, actual_type)
81
+ 'required' => property.required?,
82
+ 'schema' => schema(api, property.type)
82
83
  }
83
84
  parameter['description'] = property.description unless property.description.nil?
84
85
  parameter['example'] = property.example unless property.example.nil?
@@ -103,7 +104,7 @@ module Apigen
103
104
  response = {}
104
105
  response['description'] = output.description unless output.description.nil?
105
106
  response['example'] = output.example unless output.example.nil?
106
- if output.type != :void
107
+ if output.type != Apigen::PrimaryType.new(:void)
107
108
  response['content'] = {
108
109
  'application/json' => {
109
110
  'schema' => schema(api, output.type)
@@ -113,67 +114,12 @@ module Apigen
113
114
  [output.status.to_s, response]
114
115
  end
115
116
 
116
- def definitions(api)
117
- hash = {}
118
- api.models.each do |key, model|
119
- hash[key.to_s] = schema(api, model.type, model.description, model.example)
120
- end
121
- hash
117
+ def model_ref(type)
118
+ "#/components/schemas/#{type}"
122
119
  end
123
120
 
124
- def schema(api, type, description = nil, example = nil)
125
- schema = schema_without_description(api, type)
126
- schema['description'] = description unless description.nil?
127
- schema['example'] = example unless example.nil?
128
- schema
129
- end
130
-
131
- def schema_without_description(api, type)
132
- case type
133
- when Apigen::ObjectType
134
- object_schema(api, type)
135
- when Apigen::ArrayType
136
- array_schema(api, type)
137
- when Apigen::OptionalType
138
- raise 'OptionalType fields are only supported within object types.'
139
- when :string
140
- {
141
- 'type' => 'string'
142
- }
143
- when :int32
144
- {
145
- 'type' => 'integer',
146
- 'format' => 'int32'
147
- }
148
- when :bool
149
- {
150
- 'type' => 'boolean'
151
- }
152
- else
153
- return { '$ref' => "#/components/schemas/#{type}" } if api.models.key? type
154
- raise "Unsupported type: #{type}."
155
- end
156
- end
157
-
158
- def object_schema(api, object_type)
159
- {
160
- 'type' => 'object',
161
- 'properties' => object_type.properties.map { |name, property| object_property(api, name, property) }.to_h,
162
- 'required' => object_type.properties.reject { |_name, property| property.type.is_a? Apigen::OptionalType }.map { |name, _property| name.to_s }
163
- }
164
- end
165
-
166
- def object_property(api, name, property)
167
- # A property is never optional, because we specify which are required on the schema itself.
168
- actual_type = property.type.is_a?(Apigen::OptionalType) ? property.type.type : property.type
169
- [name.to_s, schema(api, actual_type, property.description, property.example)]
170
- end
171
-
172
- def array_schema(api, array_type)
173
- {
174
- 'type' => 'array',
175
- 'items' => schema(api, array_type.type)
176
- }
121
+ def supports_discriminator?
122
+ true
177
123
  end
178
124
  end
179
125
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require_relative './json_base'
4
5
 
5
6
  module Apigen
6
7
  module Formats
@@ -9,6 +10,8 @@ module Apigen
9
10
  # Swagger 2 (aka OpenAPI 2) generator.
10
11
  module V2
11
12
  class << self
13
+ include Apigen::Formats::JsonBase
14
+
12
15
  def generate(api)
13
16
  # TODO: Allow overriding any of the hardcoded elements.
14
17
  {
@@ -71,13 +74,11 @@ module Apigen
71
74
  end
72
75
 
73
76
  def query_parameter(api, name, property)
74
- optional = property.type.is_a?(Apigen::OptionalType)
75
- actual_type = optional ? property.type.type : property.type
76
77
  {
77
78
  'in' => 'query',
78
79
  'name' => name.to_s,
79
- 'required' => !optional
80
- }.merge(schema(api, actual_type, property.description, property.example))
80
+ 'required' => property.required?
81
+ }.merge(schema(api, property.type, property.description, property.example))
81
82
  end
82
83
 
83
84
  def input_parameter(api, property)
@@ -96,67 +97,16 @@ module Apigen
96
97
  response = {}
97
98
  response['description'] = output.description unless output.description.nil?
98
99
  response['example'] = output.example unless output.example.nil?
99
- response['schema'] = schema(api, output.type) if output.type != :void
100
+ response['schema'] = schema(api, output.type) if output.type != Apigen::PrimaryType.new(:void)
100
101
  [output.status.to_s, response]
101
102
  end
102
103
 
103
- def definitions(api)
104
- api.models.map { |key, model| [key.to_s, schema(api, model.type, model.description, model.example)] }.to_h
105
- end
106
-
107
- def schema(api, type, description = nil, example = nil)
108
- schema = schema_without_description(api, type)
109
- schema['description'] = description unless description.nil?
110
- schema['example'] = example unless example.nil?
111
- schema
112
- end
113
-
114
- def schema_without_description(api, type)
115
- case type
116
- when Apigen::ObjectType
117
- object_schema(api, type)
118
- when Apigen::ArrayType
119
- array_schema(api, type)
120
- when Apigen::OptionalType
121
- raise 'OptionalType fields are only supported within object types.'
122
- when :string
123
- {
124
- 'type' => 'string'
125
- }
126
- when :int32
127
- {
128
- 'type' => 'integer',
129
- 'format' => 'int32'
130
- }
131
- when :bool
132
- {
133
- 'type' => 'boolean'
134
- }
135
- else
136
- return { '$ref' => "#/definitions/#{type}" } if api.models.key? type
137
- raise "Unsupported type: #{type}."
138
- end
139
- end
140
-
141
- def object_schema(api, object_type)
142
- {
143
- 'type' => 'object',
144
- 'properties' => object_type.properties.map { |name, property| object_property(api, name, property) }.to_h,
145
- 'required' => object_type.properties.reject { |_name, property| property.type.is_a? Apigen::OptionalType }.map { |name, _property| name.to_s }
146
- }
104
+ def model_ref(type)
105
+ "#/definitions/#{type}"
147
106
  end
148
107
 
149
- def object_property(api, name, property)
150
- # A property is never optional, because we specify which are required on the schema itself.
151
- actual_type = property.type.is_a?(Apigen::OptionalType) ? property.type.type : property.type
152
- [name.to_s, schema(api, actual_type, property.description, property.example)]
153
- end
154
-
155
- def array_schema(api, array_type)
156
- {
157
- 'type' => 'array',
158
- 'items' => schema(api, array_type.type)
159
- }
108
+ def supports_discriminator?
109
+ true
160
110
  end
161
111
  end
162
112
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apigen
4
+ ##
5
+ # Migration is the base class for API definition migrations.
6
+ class Migration
7
+ def initialize(api)
8
+ @api = api
9
+ end
10
+
11
+ def up
12
+ raise 'Migration subclasses must implement #up.'
13
+ end
14
+
15
+ def add_endpoint(name, &block)
16
+ raise 'You must pass a block when calling `add_endpoint`.' unless block_given?
17
+ @api.endpoint(name, &block)
18
+ end
19
+
20
+ def update_endpoint(name, &block)
21
+ endpoint = @api.endpoints.find { |e| e.name == name }
22
+ raise "No such endpoint #{name}." unless endpoint
23
+ raise 'You must pass a block when calling `update_endpoint`.' unless block_given?
24
+ endpoint.instance_eval(&block)
25
+ end
26
+
27
+ def remove_endpoint(*names)
28
+ endpoints = @api.endpoints
29
+ # This is not algorithmically optimal. We won't do it millions of times though.
30
+ names.each do |name|
31
+ raise "No such endpoint :#{name}." unless endpoints.find { |e| e.name == name }
32
+ end
33
+ endpoints.reject! { |e| names.include?(e.name) }
34
+ end
35
+
36
+ def add_model(name, &block)
37
+ @api.model(name, &block)
38
+ end
39
+
40
+ def update_model(name, &block)
41
+ model = @api.models[name]
42
+ raise "No such model :#{name}." unless model
43
+ raise 'You must pass a block when calling `update_model`.' unless block_given?
44
+ model.instance_eval(&block)
45
+ end
46
+
47
+ def remove_model(*names)
48
+ models = @api.models
49
+ names.each do |name|
50
+ raise "No such model :#{name}." unless models.key? name
51
+ end
52
+ names.each { |model_name| models.delete(model_name) }
53
+ end
54
+ end
55
+ end