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