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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6432dfded34004dd760e36e95f01fba2714a01184cee2f5fa2d3de2f77c56e1
|
4
|
+
data.tar.gz: 4f5548567d427df026ca5ccdd73758ad2fde6115bc52c48785c32bd2527293c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
13
|
-
JSON.pretty_generate definitions(api)
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
13
|
+
include Apigen::Formats::JsonBase
|
17
14
|
|
18
|
-
def
|
19
|
-
|
15
|
+
def generate(api)
|
16
|
+
JSON.pretty_generate(
|
20
17
|
'$schema' => 'http://json-schema.org/draft-07/schema#',
|
21
|
-
'definitions' =>
|
22
|
-
|
18
|
+
'definitions' => definitions(api)
|
19
|
+
)
|
23
20
|
end
|
24
21
|
|
25
|
-
|
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
|
68
|
-
|
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
|
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' =>
|
81
|
-
'schema' => schema(api,
|
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
|
117
|
-
|
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
|
125
|
-
|
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' =>
|
80
|
-
}.merge(schema(api,
|
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
|
104
|
-
|
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
|
150
|
-
|
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
|