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