mortymer 0.0.7 → 0.0.8
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/mortymer/api_metadata.rb +35 -4
- data/lib/mortymer/configuration.rb +2 -1
- data/lib/mortymer/contract.rb +34 -0
- data/lib/mortymer/dependencies_dsl.rb +1 -1
- data/lib/mortymer/dry_swagger.rb +16 -9
- data/lib/mortymer/endpoint.rb +65 -15
- data/lib/mortymer/generator.rb +115 -0
- data/lib/mortymer/model.rb +14 -0
- data/lib/mortymer/moldeable.rb +15 -0
- data/lib/mortymer/openapi_generator.rb +4 -3
- data/lib/mortymer/rails/routes.rb +1 -1
- data/lib/mortymer/railtie.rb +2 -1
- data/lib/mortymer/types.rb +52 -0
- data/lib/mortymer/uploaded_file.rb +5 -0
- data/lib/mortymer/uploaded_files.rb +5 -0
- data/lib/mortymer/version.rb +1 -1
- data/lib/mortymer.rb +5 -0
- metadata +8 -3
- data/lib/mortymermer.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c13887be646cf5c591070d4d51c85e657c3509b11858d3d1cc7fa9650ec3c5ed
|
4
|
+
data.tar.gz: 0ca382da28037d53ab387f553854fc24047450b1b1d8589078f091130b15cbee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fbe4192b479624078104e07290a0e6052eb7b752a59d566ad4501c197f325c97080ec00605654f9ecdda34a16f466a71fb0ea8807a6da0e08ed0b1bee65c1fd
|
7
|
+
data.tar.gz: 7da23bf7783533a1ba3dbe56ec41a800c5e1473ab22b51bd9d1fc4ab595dae81b43f48767c145d1d429b13c601d225d1599bbaff31746b263bea68aa19be4b9c
|
@@ -50,6 +50,23 @@ module Mortymer
|
|
50
50
|
register_endpoint(:delete, input, output, path, security || @__endpoint_security__)
|
51
51
|
end
|
52
52
|
|
53
|
+
# Register an exception handler for the next endpoint
|
54
|
+
# @param exception [Class] The exception class to handle
|
55
|
+
# @param status [Symbol, Integer] The HTTP status code to return
|
56
|
+
# @param output [Class] The output schema class for the error response
|
57
|
+
# @yield [exception] Optional block to transform the exception into a response
|
58
|
+
# @yieldparam exception [Exception] The caught exception
|
59
|
+
# @yieldreturn [Hash] The response body
|
60
|
+
def handles_exception(exception, status: 400, output: nil, &block)
|
61
|
+
@__endpoint_exception_handlers__ ||= []
|
62
|
+
@__endpoint_exception_handlers__ << {
|
63
|
+
exception: exception,
|
64
|
+
status: status,
|
65
|
+
output: output,
|
66
|
+
handler: block
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
53
70
|
private
|
54
71
|
|
55
72
|
def method_added(method_name)
|
@@ -60,18 +77,30 @@ module Mortymer
|
|
60
77
|
name: reflect_method_name(method_name), action: method_name
|
61
78
|
))
|
62
79
|
input_class = @__endpoint_signature__[:input_class]
|
80
|
+
handlers = @__endpoint_signature__[:exception_handlers]
|
63
81
|
@__endpoint_signature__ = nil
|
64
82
|
return unless defined?(::Rails) && ::Rails.application.config.morty.wrap_methods
|
65
83
|
|
66
|
-
rails_wrap_method_with_no_params_call(method_name, input_class)
|
84
|
+
rails_wrap_method_with_no_params_call(method_name, input_class, handlers)
|
67
85
|
end
|
68
86
|
|
69
|
-
def rails_wrap_method_with_no_params_call(method_name, input_class)
|
87
|
+
def rails_wrap_method_with_no_params_call(method_name, input_class, handlers)
|
70
88
|
original_method = instance_method(method_name)
|
71
89
|
define_method(method_name) do
|
72
|
-
input = input_class.
|
90
|
+
input = input_class.structify(params.to_unsafe_h.to_h.deep_transform_keys(&:to_sym))
|
73
91
|
output = original_method.bind_call(self, input)
|
74
92
|
render json: output.to_h, status: :ok
|
93
|
+
rescue StandardError => e
|
94
|
+
handler = handlers.find { |h| e.is_a?(h[:exception]) }
|
95
|
+
raise unless handler
|
96
|
+
|
97
|
+
response = if handler[:handler]
|
98
|
+
handler[:handler].call(e)
|
99
|
+
else
|
100
|
+
{ error: e.message || e.class.name.underscore }
|
101
|
+
end
|
102
|
+
|
103
|
+
render json: response, status: handler[:status]
|
75
104
|
end
|
76
105
|
end
|
77
106
|
|
@@ -84,8 +113,10 @@ module Mortymer
|
|
84
113
|
path: path,
|
85
114
|
controller_class: self,
|
86
115
|
security: security,
|
87
|
-
tags: @__endpoint_tags__ || __default_tag_for_endpoint__
|
116
|
+
tags: @__endpoint_tags__ || __default_tag_for_endpoint__,
|
117
|
+
exception_handlers: [*(@__endpoint_exception_handlers__ || [])]
|
88
118
|
}
|
119
|
+
@__endpoint_exception_handlers__ = nil
|
89
120
|
end
|
90
121
|
|
91
122
|
def reflect_method_name(method_name)
|
@@ -19,7 +19,7 @@ module Mortymer
|
|
19
19
|
# Global configuration for Mortymer
|
20
20
|
class Configuration
|
21
21
|
attr_accessor :container, :serve_swagger, :swagger_title, :swagger_path, :swagger_root, :api_version,
|
22
|
-
:api_description, :security_schemes
|
22
|
+
:api_description, :security_schemes, :api_prefix
|
23
23
|
|
24
24
|
def initialize
|
25
25
|
@container = Mortymer::Container.new
|
@@ -30,6 +30,7 @@ module Mortymer
|
|
30
30
|
@api_description = "An awsome API developed with MORTYMER"
|
31
31
|
@api_version = "v1"
|
32
32
|
@security_schemes = {}
|
33
|
+
@api_prefix = "/api/v1"
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "moldeable"
|
4
|
+
require "dry/validation"
|
5
|
+
require "dry/validation/contract"
|
6
|
+
require_relative "generator"
|
7
|
+
|
8
|
+
module Mortymer
|
9
|
+
# A base model for defining schemas
|
10
|
+
class Contract < Dry::Validation::Contract
|
11
|
+
include Mortymer::Moldeable
|
12
|
+
|
13
|
+
# Exception raised when an error occours in a contract
|
14
|
+
class ContractError < StandardError
|
15
|
+
attr_reader :errors
|
16
|
+
|
17
|
+
def initialize(errors)
|
18
|
+
super
|
19
|
+
@errors = errors
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.json_schema
|
24
|
+
Generator.new.from_validation(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.structify(params)
|
28
|
+
result = new.call(params)
|
29
|
+
raise ContractError.new(result.errors.to_h) unless result.errors.empty?
|
30
|
+
|
31
|
+
result.to_h
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -52,7 +52,7 @@ module Mortymer
|
|
52
52
|
container ||= Mortymer.config&.container || Container.new
|
53
53
|
self.class.dependencies.each do |dep|
|
54
54
|
value = if overrides.key?(dep[:var_name].to_sym)
|
55
|
-
overrides
|
55
|
+
overrides.delete(dep[:var_name].to_sym)
|
56
56
|
else
|
57
57
|
container.resolve_constant(dep[:constant])
|
58
58
|
end
|
data/lib/mortymer/dry_swagger.rb
CHANGED
@@ -1,15 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry_struct_parser/struct_schema_parser"
|
4
|
+
require "dry_validation_parser/validation_schema_parser"
|
5
|
+
require "dry/swagger/documentation_generator"
|
6
|
+
require "dry/swagger/errors/missing_hash_schema_error"
|
7
|
+
require "dry/swagger/errors/missing_type_error"
|
8
|
+
require "dry/swagger/config/configuration"
|
9
|
+
require "dry/swagger/config/swagger_configuration"
|
10
|
+
require "dry/swagger/railtie" if defined?(Rails)
|
4
11
|
|
5
12
|
# Need to monkey patch the nominal visitor for dry struct parser
|
6
13
|
# as it currently does nothing with this field
|
7
14
|
module DryStructParser
|
15
|
+
# Just to monkey patch it
|
8
16
|
class StructSchemaParser
|
9
|
-
def visit_constructor(node, opts)
|
17
|
+
def visit_constructor(node, opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
10
18
|
# Handle coercible types which have the form:
|
11
19
|
# [:constructor, [[:nominal, [Type, {}]], [:method, Kernel, :Type]]]
|
12
20
|
if node[0].is_a?(Array) && node[0][0] == :nominal
|
21
|
+
if node[0][1][0] == Mortymer::Types::RackFile
|
22
|
+
keys[opts[:key]] = node[0][1][1][:swagger].merge(
|
23
|
+
required: opts.fetch(:required, true),
|
24
|
+
nullable: opts.fetch(:nullable, false)
|
25
|
+
)
|
26
|
+
return
|
27
|
+
end
|
13
28
|
required = opts.fetch(:required, true)
|
14
29
|
nullable = opts.fetch(:nullable, false)
|
15
30
|
type = node[0][1][0] # Get the type from nominal node
|
@@ -30,11 +45,3 @@ module DryStructParser
|
|
30
45
|
end
|
31
46
|
end
|
32
47
|
end
|
33
|
-
|
34
|
-
require "dry_validation_parser/validation_schema_parser"
|
35
|
-
require "dry/swagger/documentation_generator"
|
36
|
-
require "dry/swagger/errors/missing_hash_schema_error"
|
37
|
-
require "dry/swagger/errors/missing_type_error"
|
38
|
-
require "dry/swagger/config/configuration"
|
39
|
-
require "dry/swagger/config/swagger_configuration"
|
40
|
-
require "dry/swagger/railtie" if defined?(Rails)
|
data/lib/mortymer/endpoint.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "generator"
|
4
|
+
|
3
5
|
module Mortymer
|
4
6
|
# Represents an endpoint in a given system
|
5
|
-
class Endpoint
|
7
|
+
class Endpoint # rubocop:disable Metrics/ClassLength
|
6
8
|
attr_reader :http_method, :path, :input_class, :output_class, :controller_class, :action, :name
|
7
9
|
|
8
10
|
def initialize(opts = {})
|
@@ -15,6 +17,7 @@ module Mortymer
|
|
15
17
|
@action = opts[:action]
|
16
18
|
@security = opts[:security]
|
17
19
|
@tags = opts[:tags]
|
20
|
+
@exception_handlers = opts[:exception_handlers]
|
18
21
|
end
|
19
22
|
|
20
23
|
def routeable?
|
@@ -36,20 +39,11 @@ module Mortymer
|
|
36
39
|
end.join("/")
|
37
40
|
end
|
38
41
|
|
39
|
-
def generate_openapi_schema # rubocop:disable Metrics/MethodLength
|
42
|
+
def generate_openapi_schema # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
40
43
|
return unless defined?(@input_class) && defined?(@output_class)
|
41
44
|
|
42
|
-
input_schema = @input_class.respond_to?(:json_schema) ? @input_class.json_schema :
|
43
|
-
responses =
|
44
|
-
"200" => {
|
45
|
-
description: "Successful response",
|
46
|
-
content: {
|
47
|
-
"application/json" => {
|
48
|
-
schema: class_ref(@output_class)
|
49
|
-
}
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
45
|
+
input_schema = @input_class.respond_to?(:json_schema) ? @input_class.json_schema : Generator.new.from_struct(@input_class)
|
46
|
+
responses = generate_responses
|
53
47
|
|
54
48
|
# Add 422 response if there are required properties or non-string types that need coercion
|
55
49
|
if validations?(input_schema)
|
@@ -70,6 +64,7 @@ module Mortymer
|
|
70
64
|
responses: responses,
|
71
65
|
tags: @tags
|
72
66
|
}
|
67
|
+
|
73
68
|
operation[:security] = security if @security
|
74
69
|
{
|
75
70
|
path.to_s => {
|
@@ -80,6 +75,49 @@ module Mortymer
|
|
80
75
|
|
81
76
|
private
|
82
77
|
|
78
|
+
def generate_responses
|
79
|
+
responses = {
|
80
|
+
"200" => {
|
81
|
+
description: "Successful response",
|
82
|
+
content: {
|
83
|
+
"application/json" => {
|
84
|
+
schema: class_ref(@output_class)
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
@exception_handlers&.each do |handler|
|
91
|
+
status_code = if handler[:status].is_a?(Symbol)
|
92
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE[handler[:status]].to_s
|
93
|
+
else
|
94
|
+
handler[:status].to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
responses[status_code] = {
|
98
|
+
description: handler[:exception].name,
|
99
|
+
content: {
|
100
|
+
"application/json" => {
|
101
|
+
schema: if handler[:output]
|
102
|
+
class_ref(handler[:output])
|
103
|
+
else
|
104
|
+
{
|
105
|
+
type: "object",
|
106
|
+
properties: {
|
107
|
+
error: {
|
108
|
+
type: "string"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
responses
|
119
|
+
end
|
120
|
+
|
83
121
|
def validations?(schema)
|
84
122
|
return false unless schema
|
85
123
|
|
@@ -111,7 +149,7 @@ module Mortymer
|
|
111
149
|
schema = if @input_class.respond_to?(:json_schema)
|
112
150
|
@input_class.json_schema
|
113
151
|
else
|
114
|
-
|
152
|
+
Generator.new.from_struct(@input_class)
|
115
153
|
end
|
116
154
|
schema[:properties]&.map do |name, property|
|
117
155
|
{
|
@@ -126,10 +164,22 @@ module Mortymer
|
|
126
164
|
def generate_request_body
|
127
165
|
return unless @input_class && %i[post put].include?(@http_method)
|
128
166
|
|
167
|
+
schema = if @input_class.respond_to?(:json_schema)
|
168
|
+
@input_class.json_schema
|
169
|
+
else
|
170
|
+
Generator.new.from_struct(@input_class)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Check if any property is a File type
|
174
|
+
has_file = schema[:properties]&.any? do |_, property|
|
175
|
+
property[:format] == :binary
|
176
|
+
end
|
177
|
+
|
178
|
+
content_type = has_file ? "multipart/form-data" : "application/json"
|
129
179
|
{
|
130
180
|
required: true,
|
131
181
|
content: {
|
132
|
-
|
182
|
+
content_type => {
|
133
183
|
schema: class_ref(@input_class)
|
134
184
|
}
|
135
185
|
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "dry/swagger"
|
2
|
+
|
3
|
+
module Mortymer
|
4
|
+
class Generator < Dry::Swagger::DocumentationGenerator
|
5
|
+
SWAGGER_FIELD_TYPE_DEFINITIONS = {
|
6
|
+
"string" => { type: :string },
|
7
|
+
"integer" => { type: :integer },
|
8
|
+
"boolean" => { type: :boolean },
|
9
|
+
"float" => { type: :float },
|
10
|
+
"decimal" => { type: :string, format: :decimal },
|
11
|
+
"datetime" => { type: :string, format: :datetime },
|
12
|
+
"date" => { type: :string, format: :date },
|
13
|
+
"time" => { type: :string, format: :time },
|
14
|
+
"uuid" => { type: :string, format: :uuid },
|
15
|
+
"file" => { type: :string, format: :binary }
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def from_struct(struct)
|
19
|
+
generate_documentation(::DryStructParser::StructSchemaParser.new.call(struct).keys)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_validation(validation)
|
23
|
+
generate_documentation(::DryValidationParser::ValidationSchemaParser.new.call(validation).keys)
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_documentation(fields)
|
27
|
+
documentation = { properties: {}, required: [] }
|
28
|
+
fields.each do |field_name, definition|
|
29
|
+
documentation[:properties][field_name] = generate_field_properties(definition)
|
30
|
+
if definition.is_a?(Hash)
|
31
|
+
documentation[:required] << field_name if definition.fetch(:required,
|
32
|
+
true) && @config.enable_required_validation
|
33
|
+
elsif definition[0].fetch(:required, true) && @config.enable_required_validation
|
34
|
+
documentation[:required] << field_name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
{ type: :object, properties: documentation[:properties], required: documentation[:required] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_field_properties(definition)
|
42
|
+
return generate_for_sti_type(definition) if definition.is_a?(Array)
|
43
|
+
|
44
|
+
documentation = if definition[:type] == "array" || definition[:array]
|
45
|
+
generate_for_array(definition)
|
46
|
+
elsif definition[:type] == "hash"
|
47
|
+
generate_for_hash(definition)
|
48
|
+
else
|
49
|
+
generate_for_primitive_type(definition)
|
50
|
+
end
|
51
|
+
if @config.enable_nullable_validation
|
52
|
+
documentation.merge(@config.nullable_type => definition.fetch(:nullable, false))
|
53
|
+
else
|
54
|
+
documentation.merge(@config.nullable_type => true)
|
55
|
+
end
|
56
|
+
rescue KeyError
|
57
|
+
raise Errors::MissingTypeError
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate_for_sti_type(definition)
|
61
|
+
properties = {}
|
62
|
+
|
63
|
+
definition.each_with_index do |_, index|
|
64
|
+
properties["definition_#{index + 1}"] = generate_field_properties(definition[index])
|
65
|
+
end
|
66
|
+
|
67
|
+
documentation = {
|
68
|
+
type: :object,
|
69
|
+
properties: properties,
|
70
|
+
example: "Dynamic Field. See Model Definitions"
|
71
|
+
}
|
72
|
+
|
73
|
+
if definition[0][:type] == "array"
|
74
|
+
definition.each { |it| it[:type] = "hash" }
|
75
|
+
documentation[:oneOf] = definition.map { |it| generate_field_properties(it) }
|
76
|
+
{ type: :array, items: documentation }
|
77
|
+
else
|
78
|
+
documentation[:oneOf] = definition.map { |it| generate_field_properties(it) }
|
79
|
+
documentation
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_for_array(definition)
|
84
|
+
items = if array_of_primitive_type?(definition)
|
85
|
+
self.class::SWAGGER_FIELD_TYPE_DEFINITIONS.fetch(definition.fetch(:type))
|
86
|
+
else
|
87
|
+
generate_documentation(definition.fetch(:keys))
|
88
|
+
end
|
89
|
+
items = if @config.enable_nullable_validation
|
90
|
+
items.merge(@config.nullable_type => definition.fetch(:nullable, false))
|
91
|
+
else
|
92
|
+
items.merge(@config.nullable_type => true)
|
93
|
+
end
|
94
|
+
{ type: :array, items: items }
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate_for_hash(definition)
|
98
|
+
raise Errors::MissingHashSchemaError unless definition[:keys]
|
99
|
+
|
100
|
+
generate_documentation(definition.fetch(:keys))
|
101
|
+
end
|
102
|
+
|
103
|
+
def generate_for_primitive_type(definition)
|
104
|
+
documentation = self.class::SWAGGER_FIELD_TYPE_DEFINITIONS.fetch(definition.fetch(:type))
|
105
|
+
documentation = documentation.merge(enum: definition.fetch(:enum)) if definition[:enum] && @config.enable_enums
|
106
|
+
documentation = documentation.merge(description: definition.fetch(:description)) if definition[:description] &&
|
107
|
+
@config.enable_descriptions
|
108
|
+
documentation
|
109
|
+
end
|
110
|
+
|
111
|
+
def array_of_primitive_type?(definition)
|
112
|
+
definition[:array] && definition.fetch(:type) != "array"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/mortymer/model.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dry/swagger/documentation_generator"
|
4
|
+
require_relative "moldeable"
|
5
|
+
require_relative "types"
|
6
|
+
require_relative "generator"
|
7
|
+
|
3
8
|
module Mortymer
|
4
9
|
# A base model for defining schemas
|
5
10
|
class Model < Dry::Struct
|
11
|
+
include Mortymer::Moldeable
|
6
12
|
include Dry.Types()
|
13
|
+
|
14
|
+
def self.json_schema
|
15
|
+
Generator.new.from_struct(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.structify(params)
|
19
|
+
new(params)
|
20
|
+
end
|
7
21
|
end
|
8
22
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mortymer
|
4
|
+
# Interface for models to be able to be documentable
|
5
|
+
# and buildable from request inputs
|
6
|
+
module Moldeable
|
7
|
+
def self.json_schema
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.structify(params = {})
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
require_relative "endpoint_registry"
|
4
4
|
require_relative "utils/string_transformations"
|
5
|
+
require_relative "generator"
|
5
6
|
|
6
7
|
module Mortymer
|
7
8
|
# Generate an openapi doc based on the registered endpoints
|
8
9
|
class OpenapiGenerator
|
9
10
|
include Utils::StringTransformations
|
10
11
|
|
11
|
-
def initialize(prefix: "", title: "Rick on Rails API", version: "v1", description: "", registry: [],
|
12
|
+
def initialize(prefix: "", title: "Rick on Rails API", version: "v1", description: "", registry: [], # rubocop:disable Metrics/ParameterLists
|
12
13
|
security_schemes: {})
|
13
14
|
@prefix = prefix
|
14
15
|
@title = title
|
@@ -73,7 +74,7 @@ module Mortymer
|
|
73
74
|
if endpoint.input_class.respond_to?(:json_schema)
|
74
75
|
endpoint.input_class.json_schema
|
75
76
|
else
|
76
|
-
|
77
|
+
Generator.new.from_struct(endpoint.input_class)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
@@ -83,7 +84,7 @@ module Mortymer
|
|
83
84
|
if endpoint.output_class.respond_to?(:json_schema)
|
84
85
|
endpoint.output_class.json_schema
|
85
86
|
else
|
86
|
-
|
87
|
+
Generator.new.from_struct(endpoint.output_class)
|
87
88
|
end
|
88
89
|
end
|
89
90
|
schemas
|
@@ -28,7 +28,7 @@ module Mortymer
|
|
28
28
|
|
29
29
|
@drawer.send(
|
30
30
|
endpoint.http_method,
|
31
|
-
endpoint.path,
|
31
|
+
"#{Mortymer.config.api_prefix}#{endpoint.path}",
|
32
32
|
to: "#{endpoint.controller_name.gsub(/_controller$/, "")}##{endpoint.action}",
|
33
33
|
as: "#{endpoint.http_method}_#{endpoint.api_name}"
|
34
34
|
)
|
data/lib/mortymer/railtie.rb
CHANGED
@@ -27,7 +27,8 @@ module Mortymer
|
|
27
27
|
title: Mortymer.config.swagger_title,
|
28
28
|
version: Mortymer.config.api_version,
|
29
29
|
description: Mortymer.config.api_description,
|
30
|
-
security_schemes: Mortymer.config.security_schemes
|
30
|
+
security_schemes: Mortymer.config.security_schemes,
|
31
|
+
prefix: Mortymer.config.api_prefix
|
31
32
|
)
|
32
33
|
|
33
34
|
# Save OpenAPI spec to public directory
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-types"
|
4
|
+
|
5
|
+
module Mortymer
|
6
|
+
module Types
|
7
|
+
include Dry.Types()
|
8
|
+
|
9
|
+
class RackFile
|
10
|
+
attr_reader :tempfile, :original_filename, :content_type
|
11
|
+
|
12
|
+
def initialize(tempfile:, original_filename:, content_type:)
|
13
|
+
@tempfile = tempfile
|
14
|
+
@original_filename = original_filename
|
15
|
+
@content_type = content_type
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.new_from_rack_file(file)
|
19
|
+
return nil if file.nil?
|
20
|
+
|
21
|
+
new(
|
22
|
+
tempfile: file.respond_to?(:tempfile) ? file.tempfile : file[:tempfile],
|
23
|
+
original_filename: file.respond_to?(:original_filename) ? file.original_filename : file[:original_filename],
|
24
|
+
content_type: file.respond_to?(:content_type) ? file.content_type : file[:content_type]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Define a custom type for handling file uploads
|
30
|
+
UploadedFile = Types.Constructor(RackFile) do |value|
|
31
|
+
case value
|
32
|
+
when RackFile
|
33
|
+
value
|
34
|
+
when Hash
|
35
|
+
RackFile.new_from_rack_file(value)
|
36
|
+
when ActionDispatch::Http::UploadedFile
|
37
|
+
RackFile.new_from_rack_file(value)
|
38
|
+
when NilClass
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
raise Dry::Types::CoercionError, "#{value.inspect} cannot be coerced to UploadedFile"
|
42
|
+
end
|
43
|
+
end.meta(
|
44
|
+
swagger: {
|
45
|
+
type: "file"
|
46
|
+
}
|
47
|
+
)
|
48
|
+
|
49
|
+
# Array of files
|
50
|
+
UploadedFiles = Types::Array.of(File)
|
51
|
+
end
|
52
|
+
end
|
data/lib/mortymer/version.rb
CHANGED
data/lib/mortymer.rb
CHANGED
@@ -2,8 +2,13 @@
|
|
2
2
|
# typed: true
|
3
3
|
|
4
4
|
require "dry/struct"
|
5
|
+
require "mortymer/types"
|
6
|
+
require "mortymer/uploaded_file"
|
7
|
+
require "mortymer/uploaded_files"
|
5
8
|
require "mortymer/configuration"
|
9
|
+
require "mortymer/moldeable"
|
6
10
|
require "mortymer/model"
|
11
|
+
require "mortymer/contract"
|
7
12
|
require "mortymer/endpoint"
|
8
13
|
require "mortymer/dry_swagger"
|
9
14
|
require "mortymer/endpoint_registry"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mortymer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adrian Gonzalez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-03-
|
11
|
+
date: 2025-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-monads
|
@@ -126,11 +126,14 @@ files:
|
|
126
126
|
- lib/mortymer/api_metadata.rb
|
127
127
|
- lib/mortymer/configuration.rb
|
128
128
|
- lib/mortymer/container.rb
|
129
|
+
- lib/mortymer/contract.rb
|
129
130
|
- lib/mortymer/dependencies_dsl.rb
|
130
131
|
- lib/mortymer/dry_swagger.rb
|
131
132
|
- lib/mortymer/endpoint.rb
|
132
133
|
- lib/mortymer/endpoint_registry.rb
|
134
|
+
- lib/mortymer/generator.rb
|
133
135
|
- lib/mortymer/model.rb
|
136
|
+
- lib/mortymer/moldeable.rb
|
134
137
|
- lib/mortymer/openapi_generator.rb
|
135
138
|
- lib/mortymer/rails.rb
|
136
139
|
- lib/mortymer/rails/configuration.rb
|
@@ -138,9 +141,11 @@ files:
|
|
138
141
|
- lib/mortymer/rails/routes.rb
|
139
142
|
- lib/mortymer/railtie.rb
|
140
143
|
- lib/mortymer/security_schemes.rb
|
144
|
+
- lib/mortymer/types.rb
|
145
|
+
- lib/mortymer/uploaded_file.rb
|
146
|
+
- lib/mortymer/uploaded_files.rb
|
141
147
|
- lib/mortymer/utils/string_transformations.rb
|
142
148
|
- lib/mortymer/version.rb
|
143
|
-
- lib/mortymermer.rb
|
144
149
|
- mortymer.gemspec
|
145
150
|
- package-lock.json
|
146
151
|
- package.json
|
data/lib/mortymermer.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# typed: true
|
3
|
-
|
4
|
-
require "dry/struct"
|
5
|
-
require "mortymer/model"
|
6
|
-
require "mortymer/endpoint"
|
7
|
-
require "mortymer/dry_swagger"
|
8
|
-
require "mortymer/endpoint_registry"
|
9
|
-
require "mortymer/utils/string_transformations"
|
10
|
-
require "mortymer/api_metadata"
|
11
|
-
require "mortymer/openapi_generator"
|
12
|
-
require "mortymer/container"
|
13
|
-
require "mortymer/dependencies_dsl"
|
14
|
-
require "mortymer/rails" if defined?(Rails)
|
15
|
-
require "mortymer/railtie" if defined?(Rails::Railtie)
|