mortymer 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: 23f37db069ed2b34095d8c6d60bb257478af6544361fd4bc61349bdb8d764ca7
4
- data.tar.gz: 6dd9aa47b963f63de8f5f50a6e81341e4f090d0deeff244d2f8094e468438f03
3
+ metadata.gz: c454029d3e20ca350725c27aa1352769467599d1dcc06fa91bc79eda2f63c967
4
+ data.tar.gz: f1a4f441ec003b1bcc7d54ad1451fdd11ef796802931f98c1d0ee765d0758c3d
5
5
  SHA512:
6
- metadata.gz: ce8c2077ad575b4c7cc289d14d2cddbb8c3a7f8c970f57f8a57df6046a13df05dfc7836b679784b641092214ba938e028661dc57b7ef7ac281dd6c453e415ced
7
- data.tar.gz: f87678c7131a5abefe616819e42befc9b0065ac7e6d31a5e8800bf3806d63fa903e22f7f8006b6d3a10cbfcd66dbb8980a948823e59ce23a8a8144d76fb94473
6
+ metadata.gz: cde4a983859216b21cc13f9f5e582d3e47b4b6a7436fa17598afc0c69f750f0538fc0f95b71c298d876c0da03d78251e7d31868ab54d23f850dde3b1e30e6bb8
7
+ data.tar.gz: 47213cc011672d545cc97b97f2393f1064e3f658a883fec3352047034ea539991a795561879d13dea1a46fb9f953ccea0e2fa4f14875bc3759a4de1b881c898a
Binary file
@@ -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/api/books_controller.rb
113
- module Api
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
- # GET /api/books
134
- get input: Empty, output: ListAllBooksOutput
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
+ ![Swagger UI](../assets/swagg.png)
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 get(input:, output:, path: nil)
19
- register_endpoint(:get, input, output, path)
18
+ def secured_with(security)
19
+ @__endpoint_security__ = security
20
20
  end
21
21
 
22
- def post(input:, output:, path: nil)
23
- register_endpoint(:post, input, output, path)
22
+ def remove_security!
23
+ @__endpoint_security__ = nil
24
24
  end
25
25
 
26
- def put(input:, output:, path: nil)
27
- register_endpoint(:put, input, output, path)
26
+ def tags(*tag_list)
27
+ @__endpoint_tags__ = tag_list
28
28
  end
29
29
 
30
- def delete(input:, output:, path: nil)
31
- register_endpoint(:delete, input, output, path)
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
@@ -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: { schemas: generate_schemas }
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 = schema.transform_keys { |k| @prefix + k }
36
- paths.merge!(schema)
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
- Dry::Swagger::DocumentationGenerator.new.from_struct(endpoint.input_class)
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
- if endpoint.output_class && !schemas.key?(demodulize(endpoint.output_class.name))
69
- schemas[demodulize(endpoint.output_class.name)] =
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
- end
87
+ end
72
88
  end
73
89
  schemas
74
90
  end
@@ -26,7 +26,8 @@ module Mortymer
26
26
  registry: Mortymer::EndpointRegistry.registry,
27
27
  title: Mortymer.config.swagger_title,
28
28
  version: Mortymer.config.api_version,
29
- description: Mortymer.config.api_description
29
+ description: Mortymer.config.api_description,
30
+ security_schemes: Mortymer.config.security_schemes
30
31
  )
31
32
 
32
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mortymer
4
- VERSION = "0.0.6"
4
+ VERSION = "0.0.7"
5
5
  end
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.6
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-10 00:00:00.000000000 Z
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