servus 0.1.2 → 0.1.4

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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +8 -1
  4. data/IDEAS.md +5 -0
  5. data/READme.md +147 -42
  6. data/Rakefile +33 -0
  7. data/builds/servus-0.1.2.gem +0 -0
  8. data/builds/servus-0.1.3.gem +0 -0
  9. data/builds/servus-0.1.4.gem +0 -0
  10. data/docs/core/1_overview.md +77 -0
  11. data/docs/core/2_architecture.md +92 -0
  12. data/docs/core/3_service_objects.md +121 -0
  13. data/docs/features/1_schema_validation.md +119 -0
  14. data/docs/features/2_error_handling.md +121 -0
  15. data/docs/features/3_async_execution.md +81 -0
  16. data/docs/features/4_logging.md +64 -0
  17. data/docs/guides/1_common_patterns.md +90 -0
  18. data/docs/guides/2_migration_guide.md +175 -0
  19. data/docs/integration/1_configuration.md +51 -0
  20. data/docs/integration/2_testing.md +164 -0
  21. data/docs/integration/3_rails_integration.md +99 -0
  22. data/docs/yard/Servus/Base.html +1645 -0
  23. data/docs/yard/Servus/Config.html +582 -0
  24. data/docs/yard/Servus/Extensions/Async/Call.html +400 -0
  25. data/docs/yard/Servus/Extensions/Async/Errors/AsyncError.html +140 -0
  26. data/docs/yard/Servus/Extensions/Async/Errors/JobEnqueueError.html +154 -0
  27. data/docs/yard/Servus/Extensions/Async/Errors/ServiceNotFoundError.html +154 -0
  28. data/docs/yard/Servus/Extensions/Async/Errors.html +128 -0
  29. data/docs/yard/Servus/Extensions/Async/Ext.html +119 -0
  30. data/docs/yard/Servus/Extensions/Async/Job.html +310 -0
  31. data/docs/yard/Servus/Extensions/Async.html +141 -0
  32. data/docs/yard/Servus/Extensions.html +117 -0
  33. data/docs/yard/Servus/Generators/ServiceGenerator.html +261 -0
  34. data/docs/yard/Servus/Generators.html +115 -0
  35. data/docs/yard/Servus/Helpers/ControllerHelpers.html +457 -0
  36. data/docs/yard/Servus/Helpers.html +115 -0
  37. data/docs/yard/Servus/Railtie.html +134 -0
  38. data/docs/yard/Servus/Support/Errors/AuthenticationError.html +287 -0
  39. data/docs/yard/Servus/Support/Errors/BadRequestError.html +283 -0
  40. data/docs/yard/Servus/Support/Errors/ForbiddenError.html +284 -0
  41. data/docs/yard/Servus/Support/Errors/InternalServerError.html +283 -0
  42. data/docs/yard/Servus/Support/Errors/NotFoundError.html +284 -0
  43. data/docs/yard/Servus/Support/Errors/ServiceError.html +489 -0
  44. data/docs/yard/Servus/Support/Errors/ServiceUnavailableError.html +290 -0
  45. data/docs/yard/Servus/Support/Errors/UnauthorizedError.html +200 -0
  46. data/docs/yard/Servus/Support/Errors/UnprocessableEntityError.html +288 -0
  47. data/docs/yard/Servus/Support/Errors/ValidationError.html +200 -0
  48. data/docs/yard/Servus/Support/Errors.html +140 -0
  49. data/docs/yard/Servus/Support/Logger.html +856 -0
  50. data/docs/yard/Servus/Support/Rescuer/BlockContext.html +585 -0
  51. data/docs/yard/Servus/Support/Rescuer/CallOverride.html +257 -0
  52. data/docs/yard/Servus/Support/Rescuer/ClassMethods.html +343 -0
  53. data/docs/yard/Servus/Support/Rescuer.html +267 -0
  54. data/docs/yard/Servus/Support/Response.html +574 -0
  55. data/docs/yard/Servus/Support/Validator.html +1150 -0
  56. data/docs/yard/Servus/Support.html +119 -0
  57. data/docs/yard/Servus/Testing/ExampleBuilders.html +523 -0
  58. data/docs/yard/Servus/Testing/ExampleExtractor.html +578 -0
  59. data/docs/yard/Servus/Testing.html +142 -0
  60. data/docs/yard/Servus.html +343 -0
  61. data/docs/yard/_index.html +535 -0
  62. data/docs/yard/class_list.html +54 -0
  63. data/docs/yard/css/common.css +1 -0
  64. data/docs/yard/css/full_list.css +58 -0
  65. data/docs/yard/css/style.css +503 -0
  66. data/docs/yard/file.1_common_patterns.html +154 -0
  67. data/docs/yard/file.1_configuration.html +115 -0
  68. data/docs/yard/file.1_overview.html +142 -0
  69. data/docs/yard/file.1_schema_validation.html +188 -0
  70. data/docs/yard/file.2_architecture.html +157 -0
  71. data/docs/yard/file.2_error_handling.html +190 -0
  72. data/docs/yard/file.2_migration_guide.html +242 -0
  73. data/docs/yard/file.2_testing.html +227 -0
  74. data/docs/yard/file.3_async_execution.html +145 -0
  75. data/docs/yard/file.3_rails_integration.html +160 -0
  76. data/docs/yard/file.3_service_objects.html +191 -0
  77. data/docs/yard/file.4_logging.html +135 -0
  78. data/docs/yard/file.ErrorHandling.html +190 -0
  79. data/docs/yard/file.READme.html +674 -0
  80. data/docs/yard/file.architecture.html +157 -0
  81. data/docs/yard/file.async_execution.html +145 -0
  82. data/docs/yard/file.common_patterns.html +154 -0
  83. data/docs/yard/file.configuration.html +115 -0
  84. data/docs/yard/file.error_handling.html +190 -0
  85. data/docs/yard/file.logging.html +135 -0
  86. data/docs/yard/file.migration_guide.html +242 -0
  87. data/docs/yard/file.overview.html +142 -0
  88. data/docs/yard/file.rails_integration.html +160 -0
  89. data/docs/yard/file.schema_validation.html +188 -0
  90. data/docs/yard/file.service_objects.html +191 -0
  91. data/docs/yard/file.testing.html +227 -0
  92. data/docs/yard/file_list.html +119 -0
  93. data/docs/yard/frames.html +22 -0
  94. data/docs/yard/index.html +674 -0
  95. data/docs/yard/js/app.js +344 -0
  96. data/docs/yard/js/full_list.js +242 -0
  97. data/docs/yard/js/jquery.js +4 -0
  98. data/docs/yard/method_list.html +542 -0
  99. data/docs/yard/top-level-namespace.html +110 -0
  100. data/lib/generators/servus/service/service_generator.rb +64 -1
  101. data/lib/generators/servus/service/templates/service.rb.erb +1 -1
  102. data/lib/servus/base.rb +258 -57
  103. data/lib/servus/config.rb +58 -12
  104. data/lib/servus/extensions/async/call.rb +50 -18
  105. data/lib/servus/extensions/async/errors.rb +23 -3
  106. data/lib/servus/extensions/async/ext.rb +10 -2
  107. data/lib/servus/extensions/async/job.rb +32 -11
  108. data/lib/servus/helpers/controller_helpers.rb +73 -37
  109. data/lib/servus/support/errors.rb +135 -45
  110. data/lib/servus/support/rescuer.rb +189 -36
  111. data/lib/servus/support/response.rb +49 -7
  112. data/lib/servus/support/validator.rb +120 -19
  113. data/lib/servus/testing/example_builders.rb +133 -0
  114. data/lib/servus/testing/example_extractor.rb +309 -0
  115. data/lib/servus/testing.rb +17 -0
  116. data/lib/servus/version.rb +1 -1
  117. metadata +118 -19
@@ -0,0 +1,164 @@
1
+ # @title Integration / 2. Testing
2
+
3
+ # Testing
4
+
5
+ Services are designed for easy testing with explicit inputs (arguments) and outputs (Response objects). No special test infrastructure needed.
6
+
7
+ ## Schema Example Helpers
8
+
9
+ Servus provides test helpers that extract `example` values from your JSON schemas, making it easy to generate test fixtures without maintaining separate factories.
10
+
11
+ ### Setup
12
+
13
+ Include the helpers in your test suite:
14
+
15
+ ```ruby
16
+ # spec/spec_helper.rb
17
+ require 'servus/testing'
18
+
19
+ RSpec.configure do |config|
20
+ config.include Servus::Testing::ExampleBuilders
21
+ end
22
+ ```
23
+
24
+ ### Using Schema Examples
25
+
26
+ Add `example` or `examples` keywords to your schemas:
27
+
28
+ ```ruby
29
+ class ProcessPayment::Service < Servus::Base
30
+ schema(
31
+ arguments: {
32
+ type: "object",
33
+ properties: {
34
+ user_id: { type: "integer", example: 123 },
35
+ amount: { type: "number", example: 100.0 },
36
+ currency: { type: "string", examples: ["USD", "EUR", "GBP"] }
37
+ }
38
+ },
39
+ result: {
40
+ type: "object",
41
+ properties: {
42
+ transaction_id: { type: "string", example: "txn_abc123" },
43
+ status: { type: "string", example: "approved" }
44
+ }
45
+ }
46
+ )
47
+ end
48
+ ```
49
+
50
+ Then use the helpers in your tests:
51
+
52
+ ```ruby
53
+ RSpec.describe ProcessPayment::Service do
54
+ it "processes payment successfully" do
55
+ # Extract examples from schema and override specific values
56
+ args = servus_arguments_example(ProcessPayment::Service, amount: 50.0)
57
+ # => { user_id: 123, amount: 50.0, currency: "USD" }
58
+
59
+ result = ProcessPayment::Service.call(**args)
60
+
61
+ expect(result).to be_success
62
+ expect(result.data.keys).to match_array(
63
+ servus_result_example(ProcessPayment::Service).data.keys
64
+ )
65
+ end
66
+
67
+ it "handles different currencies" do
68
+ %w[USD EUR GBP].each do |currency|
69
+ result = ProcessPayment::Service.call(
70
+ **servus_arguments_example(ProcessPayment::Service, currency: currency)
71
+ )
72
+ expect(result).to be_success
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Deep Merging
79
+
80
+ Overrides are deep-merged with schema examples, allowing you to override nested values:
81
+
82
+ ```ruby
83
+ # Schema has nested structure
84
+ args = servus_arguments_example(
85
+ CreateUser::Service,
86
+ user: { profile: { age: 35 } }
87
+ )
88
+ # => { user: { id: 1, profile: { name: 'Alice', age: 35 } } }
89
+ ```
90
+
91
+ ### Available Helpers
92
+
93
+ - `servus_arguments_example(ServiceClass, **overrides)` - Returns hash of argument examples
94
+ - `servus_result_example(ServiceClass, **overrides)` - Returns Response object with result examples
95
+
96
+ ## Basic Testing Pattern
97
+
98
+ ```ruby
99
+ RSpec.describe ProcessPayment::Service do
100
+ describe ".call" do
101
+ let(:user) { create(:user, balance: 1000) }
102
+
103
+ subject(:result) { described_class.call(user_id: user.id, amount: amount) }
104
+
105
+ context "with sufficient balance" do
106
+ let(:amount) { 50 }
107
+
108
+ it "processes payment" do
109
+ expect(result.success?).to be true
110
+ expect(result.data[:new_balance]).to eq(950)
111
+ expect(result.reload.balance).to eq(950)
112
+ end
113
+ end
114
+
115
+ context "with insufficient balance" do
116
+ let(:amount) { 2000 }
117
+
118
+ it "returns failure" do
119
+ expect(result.success?).to be false
120
+ expect(result.error.message).to eq("Insufficient funds")
121
+ expect(result.error).to be_a(Servus::Support::Errors::ServiceError)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ ```
127
+
128
+ ## Testing Service Composition
129
+
130
+ When testing services that call other services, mock the child services:
131
+
132
+ ```ruby
133
+ describe Users::CreateWithAccount::Service do
134
+ # Make local or global helpers to clean up tests
135
+ def servus_success_result(data)
136
+ Servus::Support::Response.new(success: true, data: data, error: nil)
137
+ end
138
+
139
+ it "calls both create services" do
140
+ # Mock child services
141
+ allow(Users::Create::Service).to receive(:call).and_return(servus_success_result{ user: user })
142
+ allow(Accounts::Create::Service).to receive(:call).and_return(servus_success_result{ account: account })
143
+
144
+ result = described_class.call(email: "test@example.com", plan: "premium")
145
+
146
+ expect(Users::Create::Service).to have_received(:call)
147
+ expect(Accounts::Create::Service).to have_received(:call)
148
+
149
+ expect(result.success?).to be true
150
+ end
151
+ end
152
+ ```
153
+
154
+ ## Testing Schema Validation
155
+
156
+ Don't test that valid arguments pass validation - that's testing the framework. Do test that your schema catches invalid inputs:
157
+
158
+ ```ruby
159
+ it "validates required fields" do
160
+ expect {
161
+ Service.call(invalid: "params")
162
+ }.to raise_error(Servus::Support::Errors::ValidationError, /required/)
163
+ end
164
+ ```
@@ -0,0 +1,99 @@
1
+ # @title Integration / 3. Rails Integration
2
+
3
+ # Rails Integration
4
+
5
+ Servus core works in any Ruby application. Rails-specific features (async, controller helpers, generators) are optional additions that integrate with Rails conventions.
6
+
7
+ ## Controller Integration
8
+
9
+ Use the `run_service` helper to call services from controllers with automatic error handling:
10
+
11
+ ```ruby
12
+ class UsersController < ApplicationController
13
+ include Servus::Helpers::ControllerHelpers
14
+
15
+ def create
16
+ run_service(Users::Create::Service, user_params)
17
+ end
18
+
19
+ # Failures automatically render JSON:
20
+ # { "error": { "code": "validation_error", "message": "..." } }
21
+ # with appropriate HTTP status code
22
+ #
23
+ # Success will go to view and service result will be available on @result
24
+ end
25
+ ```
26
+
27
+ Without the helper, handle responses manually:
28
+
29
+ ```ruby
30
+ def create
31
+ result = Users::Create::Service.call(user_params)
32
+ if result.success?
33
+ render json: { user: result.data[:user] }, status: :created
34
+ else
35
+ render json: { error: result.error.api_error }, status: error_status(result.error)
36
+ end
37
+ end
38
+ ```
39
+
40
+ ## Generator
41
+
42
+ Generate services with specs and schema files:
43
+
44
+ ```bash
45
+ rails generate servus:service process_payment
46
+
47
+ # Creates:
48
+ # app/services/process_payment/service.rb
49
+ # spec/services/process_payment/service_spec.rb
50
+ # app/schemas/services/process_payment/arguments.json
51
+ # app/schemas/services/process_payment/result.json
52
+ ```
53
+
54
+ Schema files are optional - delete them if you don't need validation.
55
+
56
+ ## Autoloading
57
+
58
+ Servus follows Rails autoloading conventions. Services in `app/services/` are automatically loaded by Rails:
59
+
60
+ ```ruby
61
+ # app/services/users/create/service.rb
62
+ module Users
63
+ module Create
64
+ class Service < Servus::Base
65
+ # ...
66
+ end
67
+ end
68
+ end
69
+
70
+ # Rails autoloads this as Users::Create::Service
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ Configure Servus in an initializer if needed:
76
+
77
+ ```ruby
78
+ # config/initializers/servus.rb
79
+ Servus.configure do |config|
80
+ config.schema_root = Rails.root.join('config/schemas')
81
+ end
82
+ ```
83
+
84
+ Most applications don't need any configuration.
85
+
86
+ ## Background Jobs
87
+
88
+ Async execution requires ActiveJob setup. Configure your adapter:
89
+
90
+ ```ruby
91
+ # config/application.rb
92
+ config.active_job.queue_adapter = :sidekiq
93
+ ```
94
+
95
+ Then use `.call_async`:
96
+
97
+ ```ruby
98
+ Users::SendWelcomeEmail::Service.call_async(user_id: user.id)
99
+ ```