mortymer 0.0.5 → 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/docs/assets/swagg.png +0 -0
- data/docs/guide/quick-start.md +19 -23
- data/lib/mortymer/api_metadata.rb +31 -10
- data/lib/mortymer/configuration.rb +2 -1
- data/lib/mortymer/endpoint.rb +22 -7
- data/lib/mortymer/openapi_generator.rb +24 -8
- data/lib/mortymer/railtie.rb +3 -1
- data/lib/mortymer/security_schemes.rb +85 -0
- data/lib/mortymer/version.rb +1 -1
- data/lib/mortymer.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c454029d3e20ca350725c27aa1352769467599d1dcc06fa91bc79eda2f63c967
|
4
|
+
data.tar.gz: f1a4f441ec003b1bcc7d54ad1451fdd11ef796802931f98c1d0ee765d0758c3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cde4a983859216b21cc13f9f5e582d3e47b4b6a7436fa17598afc0c69f750f0538fc0f95b71c298d876c0da03d78251e7d31868ab54d23f850dde3b1e30e6bb8
|
7
|
+
data.tar.gz: 47213cc011672d545cc97b97f2393f1064e3f658a883fec3352047034ea539991a795561879d13dea1a46fb9f953ccea0e2fa4f14875bc3759a4de1b881c898a
|
Binary file
|
data/docs/guide/quick-start.md
CHANGED
@@ -27,21 +27,6 @@ Then install it:
|
|
27
27
|
bundle install
|
28
28
|
```
|
29
29
|
|
30
|
-
Mortymer requires that every class is autoloaded for it to work. This is deafault rails
|
31
|
-
behavior when running in production, but it is disabled by default on development, so,
|
32
|
-
we first need to enable eager load
|
33
|
-
|
34
|
-
```ruby
|
35
|
-
# config/environments/development.rb
|
36
|
-
Rails.application.configure do
|
37
|
-
# Settings specified here will take precedence over those in config/application.rb.
|
38
|
-
|
39
|
-
config.eager_load = true # This line is false by default, change it to true
|
40
|
-
|
41
|
-
# other configs hers ...
|
42
|
-
end
|
43
|
-
```
|
44
|
-
|
45
30
|
In rails environments, Mortymer can automatically create the routes for you, it is not necessary
|
46
31
|
that you register them one by one.
|
47
32
|
|
@@ -109,11 +94,10 @@ end
|
|
109
94
|
Set up your API controller with Mortymer's features:
|
110
95
|
|
111
96
|
```ruby
|
112
|
-
# app/controllers/
|
113
|
-
|
114
|
-
class BooksController < ApplicationController
|
115
|
-
include Mortymer::DependeciesDsl
|
97
|
+
# app/controllers/example_controller.rb
|
98
|
+
class ExampleController < ApplicationController
|
116
99
|
include Mortymer::ApiMetadata
|
100
|
+
include Mortymer::DependenciesDsl
|
117
101
|
|
118
102
|
inject BookService, as: :books
|
119
103
|
|
@@ -130,13 +114,13 @@ module Api
|
|
130
114
|
attribute :published_year, Coercible::Integer
|
131
115
|
end
|
132
116
|
|
133
|
-
|
134
|
-
|
117
|
+
tags :Books
|
118
|
+
|
119
|
+
get input: Empty, output: ListAllBooksOutput, path: "/examples/list_books"
|
135
120
|
def list_all_books(_params)
|
136
121
|
ListAllBooksOutput.new(books: @books.list_books)
|
137
122
|
end
|
138
123
|
|
139
|
-
# POST /api/books
|
140
124
|
post input: CreateBookInput, output: Book
|
141
125
|
def create_book(params)
|
142
126
|
@books.create_book(
|
@@ -145,7 +129,6 @@ module Api
|
|
145
129
|
published_year: params.published_year
|
146
130
|
)
|
147
131
|
end
|
148
|
-
end
|
149
132
|
end
|
150
133
|
```
|
151
134
|
|
@@ -182,3 +165,16 @@ This simple example showcases several of Mortymer's powerful features:
|
|
182
165
|
- ✨ **Type System**: Strong typing with `Mortymer::Models` which are powered by `Dry::Struct`
|
183
166
|
- 🔌 **Dependency Injection**: Clean service injection with `inject :book_service`
|
184
167
|
- ✅ **Parameter Validation**: Built-in request validation in controllers
|
168
|
+
|
169
|
+
## OpenAPI Documentation
|
170
|
+
|
171
|
+
Mortymer automatically generates OpenAPI (Swagger) documentation for your API endpoints. After setting up your application, you can access the Swagger UI at `/api-docs`:
|
172
|
+
|
173
|
+

|
174
|
+
|
175
|
+
This interactive documentation allows you to:
|
176
|
+
|
177
|
+
- Browse all available endpoints
|
178
|
+
- See request/response schemas
|
179
|
+
- Test API endpoints directly from the browser
|
180
|
+
- Download the OpenAPI specification
|
@@ -15,20 +15,39 @@ module Mortymer
|
|
15
15
|
|
16
16
|
# The DSL
|
17
17
|
module ClassMethods
|
18
|
-
def
|
19
|
-
|
18
|
+
def secured_with(security)
|
19
|
+
@__endpoint_security__ = security
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def remove_security!
|
23
|
+
@__endpoint_security__ = nil
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def tags(*tag_list)
|
27
|
+
@__endpoint_tags__ = tag_list
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
30
|
+
def __default_tag_for_endpoint__
|
31
|
+
# Assuming the endpoint is always defined inside a module
|
32
|
+
# the Tag would be the module of that endpoint. If no module, then
|
33
|
+
# we take the endpoint and remove any endpoint, controller suffix
|
34
|
+
[name.split("::").last(2).first] || [name.gsub(/Controller$/, "").gsub(/Endpoint$/, "")]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(input:, output:, path: nil, security: nil)
|
38
|
+
register_endpoint(:get, input, output, path, security || @__endpoint_security__)
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(input:, output:, path: nil, security: nil)
|
42
|
+
register_endpoint(:post, input, output, path, security || @__endpoint_security__)
|
43
|
+
end
|
44
|
+
|
45
|
+
def put(input:, output:, path: nil, security: nil)
|
46
|
+
register_endpoint(:put, input, output, path, security || @__endpoint_security__)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(input:, output:, path: nil, security: nil)
|
50
|
+
register_endpoint(:delete, input, output, path, security || @__endpoint_security__)
|
32
51
|
end
|
33
52
|
|
34
53
|
private
|
@@ -56,14 +75,16 @@ module Mortymer
|
|
56
75
|
end
|
57
76
|
end
|
58
77
|
|
59
|
-
def register_endpoint(http_method, input_class, output_class, path)
|
78
|
+
def register_endpoint(http_method, input_class, output_class, path, security)
|
60
79
|
@__endpoint_signature__ =
|
61
80
|
{
|
62
81
|
http_method: http_method,
|
63
82
|
input_class: input_class,
|
64
83
|
output_class: output_class,
|
65
84
|
path: path,
|
66
|
-
controller_class: self
|
85
|
+
controller_class: self,
|
86
|
+
security: security,
|
87
|
+
tags: @__endpoint_tags__ || __default_tag_for_endpoint__
|
67
88
|
}
|
68
89
|
end
|
69
90
|
|
@@ -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
|
22
|
+
:api_description, :security_schemes
|
23
23
|
|
24
24
|
def initialize
|
25
25
|
@container = Mortymer::Container.new
|
@@ -29,6 +29,7 @@ module Mortymer
|
|
29
29
|
@swagger_root = "/api-docs"
|
30
30
|
@api_description = "An awsome API developed with MORTYMER"
|
31
31
|
@api_version = "v1"
|
32
|
+
@security_schemes = {}
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
data/lib/mortymer/endpoint.rb
CHANGED
@@ -13,6 +13,8 @@ module Mortymer
|
|
13
13
|
@path = opts[:path] || infer_path_from_class
|
14
14
|
@controller_class = opts[:controller_class]
|
15
15
|
@action = opts[:action]
|
16
|
+
@security = opts[:security]
|
17
|
+
@tags = opts[:tags]
|
16
18
|
end
|
17
19
|
|
18
20
|
def routeable?
|
@@ -37,7 +39,7 @@ module Mortymer
|
|
37
39
|
def generate_openapi_schema # rubocop:disable Metrics/MethodLength
|
38
40
|
return unless defined?(@input_class) && defined?(@output_class)
|
39
41
|
|
40
|
-
input_schema = Dry::Swagger::DocumentationGenerator.new.from_struct(@input_class)
|
42
|
+
input_schema = @input_class.respond_to?(:json_schema) ? @input_class.json_schema : Dry::Swagger::DocumentationGenerator.new.from_struct(@input_class)
|
41
43
|
responses = {
|
42
44
|
"200" => {
|
43
45
|
description: "Successful response",
|
@@ -61,14 +63,17 @@ module Mortymer
|
|
61
63
|
}
|
62
64
|
end
|
63
65
|
|
66
|
+
operation = {
|
67
|
+
operation_id: operation_id,
|
68
|
+
parameters: generate_parameters,
|
69
|
+
requestBody: generate_request_body,
|
70
|
+
responses: responses,
|
71
|
+
tags: @tags
|
72
|
+
}
|
73
|
+
operation[:security] = security if @security
|
64
74
|
{
|
65
75
|
path.to_s => {
|
66
|
-
http_method.to_s =>
|
67
|
-
operation_id: operation_id,
|
68
|
-
parameters: generate_parameters,
|
69
|
-
requestBody: generate_request_body,
|
70
|
-
responses: responses
|
71
|
-
}
|
76
|
+
http_method.to_s => operation
|
72
77
|
}
|
73
78
|
}
|
74
79
|
end
|
@@ -90,6 +95,16 @@ module Mortymer
|
|
90
95
|
has_required || has_coercions
|
91
96
|
end
|
92
97
|
|
98
|
+
def security
|
99
|
+
return [] if @security.nil?
|
100
|
+
|
101
|
+
return [{ @security => [] }] if @security.is_a?(Symbol)
|
102
|
+
|
103
|
+
return [@security.scheme] if @security.respond_to?(:scheme)
|
104
|
+
|
105
|
+
[@security]
|
106
|
+
end
|
107
|
+
|
93
108
|
def generate_parameters
|
94
109
|
return [] unless @input_class && %i[get delete].include?(@http_method)
|
95
110
|
|
@@ -8,12 +8,14 @@ module Mortymer
|
|
8
8
|
class OpenapiGenerator
|
9
9
|
include Utils::StringTransformations
|
10
10
|
|
11
|
-
def initialize(prefix: "", title: "Rick on Rails API", version: "v1", description: "", registry: []
|
11
|
+
def initialize(prefix: "", title: "Rick on Rails API", version: "v1", description: "", registry: [],
|
12
|
+
security_schemes: {})
|
12
13
|
@prefix = prefix
|
13
14
|
@title = title
|
14
15
|
@version = version
|
15
16
|
@description = description
|
16
17
|
@endpoints_registry = registry
|
18
|
+
@security_schemes = security_schemes
|
17
19
|
end
|
18
20
|
|
19
21
|
def generate
|
@@ -21,7 +23,10 @@ module Mortymer
|
|
21
23
|
openapi: "3.0.1",
|
22
24
|
info: { title: @title, version: @version, description: @description },
|
23
25
|
paths: generate_paths,
|
24
|
-
components: {
|
26
|
+
components: {
|
27
|
+
schemas: generate_schemas,
|
28
|
+
securitySchemes: @security_schemes
|
29
|
+
}
|
25
30
|
}
|
26
31
|
end
|
27
32
|
|
@@ -32,8 +37,11 @@ module Mortymer
|
|
32
37
|
next unless endpoint.routeable?
|
33
38
|
|
34
39
|
schema = endpoint.generate_openapi_schema || {}
|
35
|
-
schema
|
36
|
-
|
40
|
+
schema.each do |path, methods|
|
41
|
+
prefixed_path = @prefix + path
|
42
|
+
paths[prefixed_path] ||= {}
|
43
|
+
paths[prefixed_path].merge!(methods)
|
44
|
+
end
|
37
45
|
end || {}
|
38
46
|
end
|
39
47
|
|
@@ -62,13 +70,21 @@ module Mortymer
|
|
62
70
|
|
63
71
|
if endpoint.input_class && !schemas.key?(demodulize(endpoint.input_class.name))
|
64
72
|
schemas[demodulize(endpoint.input_class.name)] =
|
65
|
-
|
73
|
+
if endpoint.input_class.respond_to?(:json_schema)
|
74
|
+
endpoint.input_class.json_schema
|
75
|
+
else
|
76
|
+
Dry::Swagger::DocumentationGenerator.new.from_struct(endpoint.input_class)
|
77
|
+
end
|
66
78
|
end
|
67
79
|
|
68
|
-
|
69
|
-
|
80
|
+
next unless endpoint.output_class && !schemas.key?(demodulize(endpoint.output_class.name))
|
81
|
+
|
82
|
+
schemas[demodulize(endpoint.output_class.name)] =
|
83
|
+
if endpoint.output_class.respond_to?(:json_schema)
|
84
|
+
endpoint.output_class.json_schema
|
85
|
+
else
|
70
86
|
Dry::Swagger::DocumentationGenerator.new.from_struct(endpoint.output_class)
|
71
|
-
|
87
|
+
end
|
72
88
|
end
|
73
89
|
schemas
|
74
90
|
end
|
data/lib/mortymer/railtie.rb
CHANGED
@@ -14,6 +14,7 @@ module Mortymer
|
|
14
14
|
if ::Rails.application.config.cache_classes == false
|
15
15
|
app.reloader.before_class_unload do
|
16
16
|
Mortymer::EndpointRegistry.clear
|
17
|
+
Mortymer.config.container.registry.clear
|
17
18
|
end
|
18
19
|
|
19
20
|
app.reloader.to_prepare do
|
@@ -25,7 +26,8 @@ module Mortymer
|
|
25
26
|
registry: Mortymer::EndpointRegistry.registry,
|
26
27
|
title: Mortymer.config.swagger_title,
|
27
28
|
version: Mortymer.config.api_version,
|
28
|
-
description: Mortymer.config.api_description
|
29
|
+
description: Mortymer.config.api_description,
|
30
|
+
security_schemes: Mortymer.config.security_schemes
|
29
31
|
)
|
30
32
|
|
31
33
|
# Save OpenAPI spec to public directory
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mortymer
|
4
|
+
module SecuritySchemes
|
5
|
+
# Define the Bearer Auth security scheme
|
6
|
+
class Bearer
|
7
|
+
def self.scheme(scopes = [].freeze)
|
8
|
+
{ BearerAuth: scopes }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.to_scheme
|
12
|
+
{
|
13
|
+
BearerAuth: {
|
14
|
+
type: :http,
|
15
|
+
scheme: :bearer
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define the API Key security scheme
|
22
|
+
class ApiKey
|
23
|
+
def self.scheme(scopes = [].freeze)
|
24
|
+
{ ApiKeyAuth: scopes }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.to_scheme(name: "X-API-Key", in: :header)
|
28
|
+
{
|
29
|
+
ApiKeyAuth: {
|
30
|
+
type: :apiKey,
|
31
|
+
name: name,
|
32
|
+
in: binding.local_variable_get(:in) # Use binding because 'in' is a reserved word
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Define the Basic Auth security scheme
|
39
|
+
class Basic
|
40
|
+
def self.scheme(scopes = [].freeze)
|
41
|
+
{ BasicAuth: scopes }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.to_scheme
|
45
|
+
{
|
46
|
+
BasicAuth: {
|
47
|
+
type: :http,
|
48
|
+
scheme: :basic
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define the OAuth2 security scheme
|
55
|
+
class OAuth2
|
56
|
+
def self.scheme(scopes = [].freeze)
|
57
|
+
{ OAuth2: scopes }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.to_scheme(flows: {})
|
61
|
+
{
|
62
|
+
OAuth2: {
|
63
|
+
type: :oauth2,
|
64
|
+
flows: flows || {
|
65
|
+
implicit: {
|
66
|
+
authorizationUrl: "/oauth/authorize",
|
67
|
+
scopes: {
|
68
|
+
"read:api" => "Read access",
|
69
|
+
"write:api" => "Write access"
|
70
|
+
}
|
71
|
+
},
|
72
|
+
password: {
|
73
|
+
tokenUrl: "/oauth/token",
|
74
|
+
scopes: {
|
75
|
+
"read:api" => "Read access",
|
76
|
+
"write:api" => "Write access"
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/mortymer/version.rb
CHANGED
data/lib/mortymer.rb
CHANGED
@@ -12,5 +12,6 @@ require "mortymer/api_metadata"
|
|
12
12
|
require "mortymer/openapi_generator"
|
13
13
|
require "mortymer/container"
|
14
14
|
require "mortymer/dependencies_dsl"
|
15
|
+
require "mortymer/security_schemes"
|
15
16
|
require "mortymer/rails" if defined?(Rails)
|
16
17
|
require "mortymer/railtie" if defined?(Rails::Railtie)
|
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.7
|
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-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-monads
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- docs/.vitepress/theme/style.css
|
117
117
|
- docs/advanced/dependency-injection.md
|
118
118
|
- docs/advanced/openapi.md
|
119
|
+
- docs/assets/swagg.png
|
119
120
|
- docs/guide/api-metadata.md
|
120
121
|
- docs/guide/introduction.md
|
121
122
|
- docs/guide/models.md
|
@@ -136,6 +137,7 @@ files:
|
|
136
137
|
- lib/mortymer/rails/endpoint_wrapper_controller.rb
|
137
138
|
- lib/mortymer/rails/routes.rb
|
138
139
|
- lib/mortymer/railtie.rb
|
140
|
+
- lib/mortymer/security_schemes.rb
|
139
141
|
- lib/mortymer/utils/string_transformations.rb
|
140
142
|
- lib/mortymer/version.rb
|
141
143
|
- lib/mortymermer.rb
|