servus 0.0.1
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/READme.md +379 -0
- data/Rakefile +12 -0
- data/lib/generators/servus/service/service_generator.rb +63 -0
- data/lib/generators/servus/service/templates/arguments.json.erb +15 -0
- data/lib/generators/servus/service/templates/result.json.erb +4 -0
- data/lib/generators/servus/service/templates/service.rb.erb +12 -0
- data/lib/generators/servus/service/templates/service_spec.rb.erb +9 -0
- data/lib/servus/base.rb +76 -0
- data/lib/servus/config.rb +43 -0
- data/lib/servus/railtie.rb +8 -0
- data/lib/servus/support/errors.rb +135 -0
- data/lib/servus/support/logger.rb +74 -0
- data/lib/servus/support/response.rb +19 -0
- data/lib/servus/support/validator.rb +81 -0
- data/lib/servus/version.rb +5 -0
- data/lib/servus.rb +24 -0
- data/sig/servus.rbs +4 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 36f4d4b15ff9038d9c039f6efb0fe5027c6326811334dc3e478a906b31286d5d
|
|
4
|
+
data.tar.gz: 786b002fe1937d5204088fdb982156bc1fb1ce7bc1c20f2ecd4c737e23e1acd5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 04ce3e7a6cdd2371c4da93d98123ea640736856638dfa5c2a60d3c4b4c7ae1109bd597bf577cec892461677a906e0955ee782c20532901b3808eca80c6ccbb6a
|
|
7
|
+
data.tar.gz: 9c07175986bd05753df2b29dbc70f854cbab12745183d6e8937cc89dd46e5e819df588ac0ef88ea2db8298cb6e470e798a8e3361cc8765194ddee1244c7a2d1c
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sebastian Scholl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/READme.md
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
## Servus Gem
|
|
2
|
+
|
|
3
|
+
Servus is a gem for creating and managing service objects. It includes:
|
|
4
|
+
|
|
5
|
+
- A base class for service objects
|
|
6
|
+
- Generators for core service objects and specs
|
|
7
|
+
- Support for schema validation
|
|
8
|
+
- Support for error handling
|
|
9
|
+
- Support for logging
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Generators
|
|
14
|
+
|
|
15
|
+
Service objects can be easily created using the `rails g servus:service namespace/service_name [*params]` command. For sake of consistency, use this command when generating new service objects.
|
|
16
|
+
|
|
17
|
+
### Generate Service
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
$ rails g servus:service namespace/do_something_helpful user
|
|
21
|
+
=> create app/services/namespace/do_something_helpful/service.rb
|
|
22
|
+
create spec/services/namespace/do_something_helpful/service_spec.rb
|
|
23
|
+
create app/schemas/services/namespace/do_something_helpful/result.json
|
|
24
|
+
create app/schemas/services/namespace/do_something_helpful/arguments.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Destroy Service
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ rails d servus:service namespace/do_something_helpful
|
|
31
|
+
=> remove app/services/namespace/do_something_helpful/service.rb
|
|
32
|
+
remove spec/services/namespace/do_something_helpful/service_spec.rb
|
|
33
|
+
remove app/schemas/services/namespace/do_something_helpful/result.json
|
|
34
|
+
remove app/schemas/services/namespace/do_something_helpful/arguments.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Arguments
|
|
38
|
+
|
|
39
|
+
Service objects should use keyword arguments rather than positional arguments for improved clarity and more meaningful error messages.
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# Good ✅
|
|
43
|
+
class Services::ProcessPayment::Service < Servus::Base
|
|
44
|
+
def initialize(user:, amount:, payment_method:)
|
|
45
|
+
@user = user
|
|
46
|
+
@amount = amount
|
|
47
|
+
@payment_method = payment_method
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Bad ❌
|
|
52
|
+
class Services::ProcessPayment::Service < Servus::Base
|
|
53
|
+
def initialize(user, amount, payment_method)
|
|
54
|
+
@user = user
|
|
55
|
+
@amount = amount
|
|
56
|
+
@payment_method = payment_method
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Directory Structure
|
|
62
|
+
|
|
63
|
+
Each service belongs in its own namespace with this structure:
|
|
64
|
+
|
|
65
|
+
- `app/services/service_name/service.rb` - Main class/entry point
|
|
66
|
+
- `app/services/service_name/support/` - Service-specific supporting classes
|
|
67
|
+
|
|
68
|
+
Supporting classes should never be used outside their parent service.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
app/services/
|
|
72
|
+
├── process_payment/
|
|
73
|
+
│ ├── service.rb
|
|
74
|
+
│ └── support/
|
|
75
|
+
│ ├── payment_validator.rb
|
|
76
|
+
│ └── receipt_generator.rb
|
|
77
|
+
├── generate_report/
|
|
78
|
+
│ ├── service.rb
|
|
79
|
+
│ └── support/
|
|
80
|
+
│ ├── report_formatter.rb
|
|
81
|
+
│ └── data_collector.rb
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## **Methods**
|
|
85
|
+
|
|
86
|
+
Every service object must implement:
|
|
87
|
+
|
|
88
|
+
- An `initialize` method that sets instance variables
|
|
89
|
+
- A parameter-less `call` instance method that executes the service logic
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
class Services::GenerateReport::Service < Servus::Base
|
|
93
|
+
def initialize(user:, report_type:, date_range:)
|
|
94
|
+
@user = user
|
|
95
|
+
@report_type = report_type
|
|
96
|
+
@date_range = date_range
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def call
|
|
100
|
+
data = collect_data
|
|
101
|
+
if data.empty?
|
|
102
|
+
return failure("No data available for the selected date range")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
formatted_report = format_report(data)
|
|
106
|
+
success(formatted_report)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def collect_data
|
|
112
|
+
# Implementation details...
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def format_report(data)
|
|
116
|
+
# Implementation details...
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## **Inheritance**
|
|
123
|
+
|
|
124
|
+
- Every main service class (`service.rb`) must inherit from `Servus::Base`
|
|
125
|
+
- Supporting classes should NOT inherit from `Servus::Base`
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
# Good ✅
|
|
129
|
+
class Services::NotifyUser::Service < Servus::Base
|
|
130
|
+
# Service implementation
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class Services::NotifyUser::Support::MessageBuilder
|
|
134
|
+
# Support class implementation (does NOT inherit from BaseService)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Bad ❌
|
|
138
|
+
class Services::NotifyUser::Support::MessageBuilder < Servus::Base
|
|
139
|
+
# Incorrect: support classes should not inherit from Base class
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## **Call Chain**
|
|
144
|
+
|
|
145
|
+
Always use the class method `call` instead of manual instantiation. The `call` method:
|
|
146
|
+
|
|
147
|
+
1. Initializes an instance of the service using provided keyword arguments
|
|
148
|
+
2. Calls the instance-level `call` method
|
|
149
|
+
3. Handles schema validation of inputs and outputs
|
|
150
|
+
4. Handles logging of inputs and results
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Good ✅
|
|
154
|
+
result = Services::ProcessPayment::Service.call(
|
|
155
|
+
amount: 50,
|
|
156
|
+
user_id: 123,
|
|
157
|
+
payment_method: "credit_card"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Bad ❌ - bypasses logging and other class-level functionality
|
|
161
|
+
service = Services::ProcessPayment::Service.new(
|
|
162
|
+
amount: 50,
|
|
163
|
+
user_id: 123,
|
|
164
|
+
payment_method: "credit_card"
|
|
165
|
+
)
|
|
166
|
+
result = service.call
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
When services call other services, always use the class-level `call` method:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
def process_order
|
|
174
|
+
# Good ✅
|
|
175
|
+
payment_result = Services::ProcessPayment::Service.call(
|
|
176
|
+
amount: @order.total,
|
|
177
|
+
payment_method: @payment_details
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Bad ❌
|
|
181
|
+
payment_service = Services::ProcessPayment::Service.new(
|
|
182
|
+
amount: @order.total,
|
|
183
|
+
payment_method: @payment_details
|
|
184
|
+
)
|
|
185
|
+
payment_result = payment_service.call
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## **Responses**
|
|
191
|
+
|
|
192
|
+
The `Servus::Base` provides standardized response methods:
|
|
193
|
+
|
|
194
|
+
- `success(data)` - Returns success with data as a single argument
|
|
195
|
+
- `failure(message, **options)` - Logs error and returns failure response
|
|
196
|
+
- `error!(message)` - Logs error and raises exception
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
def call
|
|
200
|
+
# Return failure with message
|
|
201
|
+
return failure("Order is not in a pending state") unless @order.pending?
|
|
202
|
+
|
|
203
|
+
# Do something important
|
|
204
|
+
|
|
205
|
+
# Process and return success with single data object
|
|
206
|
+
success({
|
|
207
|
+
order_id: @order.id,
|
|
208
|
+
status: "processed",
|
|
209
|
+
timestamp: Time.now
|
|
210
|
+
})
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
All responses are `Servus::Support::Response` objects with a `success?` boolean attribute and either `data` (for success) or `error` (for error) attributes.
|
|
215
|
+
|
|
216
|
+
### Service Error Returns and Handling
|
|
217
|
+
|
|
218
|
+
By default, the `failure(...)` method creates an instance of `ServiceError` and adds it to the response type's `error` attribute. Standard and custom error types should inherit from the `ServiceError` class and optionally implement a custom `api_error` method. This enables developers to choose between using an API-specific error or generic error message in the calling context.
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
# Called from within a Service Object
|
|
222
|
+
class SomeServiceObject::Service < Servus::Base
|
|
223
|
+
def call
|
|
224
|
+
# Return default ServiceError with custom message
|
|
225
|
+
failure("That didn't work for some reason")
|
|
226
|
+
#=> Response(false, nil, ApplicationService::Support::Errors::ServiceError("That didn't work for some reason"))
|
|
227
|
+
#
|
|
228
|
+
# OR
|
|
229
|
+
#
|
|
230
|
+
# Specify ServiceError type with custom message
|
|
231
|
+
failure("Custom message", type: Servus::Support::Errors::NotFoundError)
|
|
232
|
+
#=> Response(false, nil, ApplicationService::Support::Errors::NotFoundError("Custom message"))
|
|
233
|
+
#
|
|
234
|
+
# OR
|
|
235
|
+
#
|
|
236
|
+
# Specify ServiceError type with default message
|
|
237
|
+
failure(type: Servus::Support::Errors::NotFoundError)
|
|
238
|
+
#=> Response(false, nil, ApplicationService::Support::Errors::NotFoundError("Record not found"))
|
|
239
|
+
#
|
|
240
|
+
# OR
|
|
241
|
+
#
|
|
242
|
+
# Accept all defaults
|
|
243
|
+
failure
|
|
244
|
+
#=> Response(false, nil, ApplicationService::Support::Errors::ServiceError("An error occurred"))
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Error handling in parent context
|
|
249
|
+
class SomeController < AppController
|
|
250
|
+
def controller_action
|
|
251
|
+
result = SomeServiceObject::Service.call(arg: 1)
|
|
252
|
+
|
|
253
|
+
return if result.success?
|
|
254
|
+
|
|
255
|
+
# If you just want the error message
|
|
256
|
+
bad_request(result.error.message)
|
|
257
|
+
|
|
258
|
+
# If you want the API error
|
|
259
|
+
service_object_error(result.error.api_error)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## **Schema Validation**
|
|
265
|
+
|
|
266
|
+
Service objects support two methods for schema validation: JSON Schema files and inline schema declarations.
|
|
267
|
+
|
|
268
|
+
### 1. File-based Schema Validation
|
|
269
|
+
|
|
270
|
+
Every service can have corresponding schema files in the centralized schema directory:
|
|
271
|
+
|
|
272
|
+
- `app/schemas/services/service_name/arguments.json` - Validates input arguments
|
|
273
|
+
- `app/schemas/services/service_name/result.json` - Validates success response data
|
|
274
|
+
|
|
275
|
+
Example `arguments.json`:
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"type": "object",
|
|
280
|
+
"required": ["user_id", "amount", "payment_method"],
|
|
281
|
+
"properties": {
|
|
282
|
+
"user_id": { "type": "integer" },
|
|
283
|
+
"amount": {
|
|
284
|
+
"type": "integer",
|
|
285
|
+
"minimum": 1
|
|
286
|
+
},
|
|
287
|
+
"payment_method": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"enum": ["credit_card", "paypal", "bank_transfer"]
|
|
290
|
+
},
|
|
291
|
+
"currency": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"default": "USD"
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
"additionalProperties": false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Example `result.json`:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"type": "object",
|
|
306
|
+
"required": ["transaction_id", "status"],
|
|
307
|
+
"properties": {
|
|
308
|
+
"transaction_id": { "type": "string" },
|
|
309
|
+
"status": {
|
|
310
|
+
"type": "string",
|
|
311
|
+
"enum": ["approved", "pending", "declined"]
|
|
312
|
+
},
|
|
313
|
+
"receipt_url": { "type": "string" }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 2. Inline Schema Validation
|
|
320
|
+
|
|
321
|
+
Alternatively, schemas can be declared directly within the service class using `ARGUMENTS_SCHEMA` and `RESULT_SCHEMA` constants.
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
class Services::ProcessPayment::Service < Servus::Base
|
|
325
|
+
ARGUMENTS_SCHEMA = {
|
|
326
|
+
type: "object",
|
|
327
|
+
required: ["user_id", "amount", "payment_method"],
|
|
328
|
+
properties: {
|
|
329
|
+
user_id: { type: "integer" },
|
|
330
|
+
amount: {
|
|
331
|
+
type: "integer",
|
|
332
|
+
minimum: 1
|
|
333
|
+
},
|
|
334
|
+
payment_method: {
|
|
335
|
+
type: "string",
|
|
336
|
+
enum: ["credit_card", "paypal", "bank_transfer"]
|
|
337
|
+
},
|
|
338
|
+
currency: {
|
|
339
|
+
type: "string",
|
|
340
|
+
default: "USD"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
additionalProperties: false
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
RESULT_SCHEMA = {
|
|
347
|
+
type: "object",
|
|
348
|
+
required: ["transaction_id", "status"],
|
|
349
|
+
properties: {
|
|
350
|
+
transaction_id: { type: "string" },
|
|
351
|
+
status: {
|
|
352
|
+
type: "string",
|
|
353
|
+
enum: ["approved", "pending", "declined"]
|
|
354
|
+
},
|
|
355
|
+
receipt_url: { type: "string" }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
end
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
These schemas use JSON Schema format to enforce type safety and input/output contracts. For detailed information on authoring JSON Schema files, refer to the official specification at: https://json-schema.org/specification.html
|
|
364
|
+
|
|
365
|
+
### Schema Resolution
|
|
366
|
+
|
|
367
|
+
The validation system follows this precedence:
|
|
368
|
+
|
|
369
|
+
1. Checks for inline schema constants (`ARGUMENTS_SCHEMA` or `RESULT_SCHEMA`)
|
|
370
|
+
2. Falls back to JSON files if no inline schema is found
|
|
371
|
+
3. Returns nil if neither exists
|
|
372
|
+
|
|
373
|
+
### Schema Caching
|
|
374
|
+
|
|
375
|
+
Both file-based and inline schemas are automatically cached:
|
|
376
|
+
|
|
377
|
+
- First validation request loads and caches the schema
|
|
378
|
+
- Subsequent validations use the cached version
|
|
379
|
+
- Cache can be cleared using `SchemaValidation.clear_cache!`
|
data/Rakefile
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Generators
|
|
5
|
+
# Servus Generator
|
|
6
|
+
class ServiceGenerator < Rails::Generators::NamedBase
|
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
|
8
|
+
|
|
9
|
+
argument :parameters, type: :array, default: [], banner: "parameter"
|
|
10
|
+
|
|
11
|
+
def create_service_file
|
|
12
|
+
template "service.rb.erb", service_path
|
|
13
|
+
template "service_spec.rb.erb", service_path_spec
|
|
14
|
+
|
|
15
|
+
# Template json schemas
|
|
16
|
+
template "result.json.erb", service_result_schema_path
|
|
17
|
+
template "arguments.json.erb", service_arguments_shecma_path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def service_path
|
|
23
|
+
"app/services/#{file_path}/service.rb"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def service_path_spec
|
|
27
|
+
"spec/services/#{file_path}/service_spec.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def service_result_schema_path
|
|
31
|
+
"app/schemas/services/#{file_path}/result.json"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def service_arguments_shecma_path
|
|
35
|
+
"app/schemas/services/#{file_path}/arguments.json"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def service_class_name
|
|
39
|
+
"#{class_name}::Service"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def service_full_class_name
|
|
43
|
+
service_class_name.include?("::") ? service_class_name : "::#{service_class_name}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parameter_list
|
|
47
|
+
return "" if parameters.empty?
|
|
48
|
+
|
|
49
|
+
"(#{parameters.map { |param| "#{param}:" }.join(", ")})"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def initialize_params
|
|
53
|
+
parameters.map { |param| "@#{param} = #{param}" }.join("\n ")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def attr_readers
|
|
57
|
+
return "" if parameters.empty?
|
|
58
|
+
|
|
59
|
+
"attr_reader #{parameters.map { |param| ":#{param}" }.join(", ")}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/servus/base.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
class Base
|
|
5
|
+
include Servus::Support::Errors
|
|
6
|
+
|
|
7
|
+
# Support class aliases
|
|
8
|
+
Logger = Servus::Support::Logger
|
|
9
|
+
Response = Servus::Support::Response
|
|
10
|
+
Validator = Servus::Support::Validator
|
|
11
|
+
|
|
12
|
+
# Calls the service and returns a response
|
|
13
|
+
# @param args [Hash] The arguments to pass to the service
|
|
14
|
+
# @return [Servus::Support::Response] The response
|
|
15
|
+
# @raise [StandardError] If an exception is raised
|
|
16
|
+
# @raise [Servus::Support::Errors::ValidationError] If result is invalid
|
|
17
|
+
# @raise [Servus::Support::Errors::ValidationError] If arguments are invalid
|
|
18
|
+
def self.call(**args)
|
|
19
|
+
Logger.log_call(self, args)
|
|
20
|
+
|
|
21
|
+
Validator.validate_arguments!(self, args)
|
|
22
|
+
|
|
23
|
+
result = benchmark(**args) do
|
|
24
|
+
new(**args).call
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Validator.validate_result!(self, result)
|
|
28
|
+
|
|
29
|
+
result
|
|
30
|
+
rescue ValidationError => e
|
|
31
|
+
Logger.log_validation_error(self, e)
|
|
32
|
+
raise e
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
Logger.log_exception(self, e)
|
|
35
|
+
raise e
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns a success response
|
|
39
|
+
# @param data [Object] The data to return
|
|
40
|
+
# @return [Servus::Support::Response] The success response
|
|
41
|
+
def success(data)
|
|
42
|
+
Response.new(true, data, nil)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a failure response
|
|
46
|
+
# @param message [String] The error message
|
|
47
|
+
# @param type [Class] The error type
|
|
48
|
+
# @return [Servus::Support::Response] The failure response
|
|
49
|
+
def failure(message = nil, type: Servus::Support::Errors::ServiceError)
|
|
50
|
+
error = type.new(message)
|
|
51
|
+
Response.new(false, nil, error)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Raises an error and logs it
|
|
55
|
+
# @param message [String] The error message
|
|
56
|
+
# @param type [Class] The error type
|
|
57
|
+
# @return [void]
|
|
58
|
+
def error!(message = nil, type: Servus::Support::Errors::ServiceError)
|
|
59
|
+
Logger.log_exception(self.class, type.new(message))
|
|
60
|
+
raise type, message
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Benchmarks the call
|
|
64
|
+
# @param args [Hash] The arguments to pass to the service
|
|
65
|
+
# @return [Object] The result of the call
|
|
66
|
+
def self.benchmark(**_args)
|
|
67
|
+
start_time = Time.now.utc
|
|
68
|
+
result = yield
|
|
69
|
+
duration = Time.now.utc - start_time
|
|
70
|
+
|
|
71
|
+
Logger.log_result(self, result, duration)
|
|
72
|
+
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
class Config
|
|
5
|
+
# The directory where schemas are loaded from, can be set by the user
|
|
6
|
+
attr_reader :schema_root
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
# Default to Rails.root if available, otherwise use current working directory
|
|
10
|
+
@schema_root = if defined?(Rails)
|
|
11
|
+
Rails.root.join("app/schemas/services")
|
|
12
|
+
else
|
|
13
|
+
File.expand_path("../../../app/schemas/services", __dir__)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns the path for a specific service's schema
|
|
18
|
+
#
|
|
19
|
+
# @param service_namespace [String] the namespace of the service
|
|
20
|
+
# @param type [String] the type of the schema (e.g., "arguments", "result")
|
|
21
|
+
# @return [String] the path for the service's schema type
|
|
22
|
+
def schema_path_for(service_namespace, type)
|
|
23
|
+
File.join(schema_root.to_s, service_namespace, "#{type}.json")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the directory for a specific service
|
|
27
|
+
#
|
|
28
|
+
# @param service_namespace [String] the namespace of the service
|
|
29
|
+
# @return [String] the directory for the service's schemas
|
|
30
|
+
def schema_dir_for(service_namespace)
|
|
31
|
+
File.join(schema_root.to_s, service_namespace)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Singleton config instance
|
|
36
|
+
def self.config
|
|
37
|
+
@config ||= Config.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.configure
|
|
41
|
+
yield(config)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Support
|
|
5
|
+
module Errors
|
|
6
|
+
# Base error class for application services
|
|
7
|
+
# @param message [String] The error message
|
|
8
|
+
# @return [ServiceError] The error instance
|
|
9
|
+
class ServiceError < StandardError
|
|
10
|
+
attr_reader :message
|
|
11
|
+
|
|
12
|
+
DEFAULT_MESSAGE = "An error occurred"
|
|
13
|
+
|
|
14
|
+
# Initializes a new error instance
|
|
15
|
+
# @param message [String] The error message
|
|
16
|
+
# @return [ServiceError] The error instance
|
|
17
|
+
def initialize(message = nil)
|
|
18
|
+
@message = message || self.class::DEFAULT_MESSAGE
|
|
19
|
+
super("#{self.class}: #{message}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 404 error response
|
|
23
|
+
# @return [Hash] The error response
|
|
24
|
+
def api_error
|
|
25
|
+
{ code: :bad_request, message: message }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Error class for bad request errors
|
|
30
|
+
# @param message [String] The error message
|
|
31
|
+
# @return [BadRequestError] The error instance
|
|
32
|
+
class BadRequestError < ServiceError
|
|
33
|
+
DEFAULT_MESSAGE = "Bad request"
|
|
34
|
+
|
|
35
|
+
# 400 error response
|
|
36
|
+
# @return [Hash] The error response
|
|
37
|
+
def api_error
|
|
38
|
+
{ code: :bad_request, message: message }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Error class for authentication errors
|
|
43
|
+
# @param message [String] The error message
|
|
44
|
+
# @return [AuthenticationError] The error instance
|
|
45
|
+
class AuthenticationError < ServiceError
|
|
46
|
+
DEFAULT_MESSAGE = "Authentication failed"
|
|
47
|
+
|
|
48
|
+
# 401 error response
|
|
49
|
+
# @return [Hash] The error response
|
|
50
|
+
def api_error
|
|
51
|
+
{ code: :unauthorized, message: message }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Error class for unauthorized errors
|
|
56
|
+
# @param message [String] The error message
|
|
57
|
+
# @return [UnauthorizedError] The error instance
|
|
58
|
+
class UnauthorizedError < AuthenticationError
|
|
59
|
+
DEFAULT_MESSAGE = "Unauthorized"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Error class for forbidden errors
|
|
63
|
+
# @param message [String] The error message
|
|
64
|
+
# @return [ForbiddenError] The error instance
|
|
65
|
+
class ForbiddenError < ServiceError
|
|
66
|
+
DEFAULT_MESSAGE = "Forbidden"
|
|
67
|
+
|
|
68
|
+
# 403 error response
|
|
69
|
+
# @return [Hash] The error response
|
|
70
|
+
def api_error
|
|
71
|
+
{ code: :forbidden, message: message }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Error class for not found errors
|
|
76
|
+
# @param message [String] The error message
|
|
77
|
+
# @return [NotFoundError] The error instance
|
|
78
|
+
class NotFoundError < ServiceError
|
|
79
|
+
DEFAULT_MESSAGE = "Not found"
|
|
80
|
+
|
|
81
|
+
# 404 error response
|
|
82
|
+
# @return [Hash] The error response
|
|
83
|
+
def api_error
|
|
84
|
+
{ code: :not_found, message: message }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Error class for unprocessable entity errors
|
|
89
|
+
# @param message [String] The error message
|
|
90
|
+
# @return [UnprocessableEntityError] The error instance
|
|
91
|
+
class UnprocessableEntityError < ServiceError
|
|
92
|
+
DEFAULT_MESSAGE = "Unprocessable entity"
|
|
93
|
+
|
|
94
|
+
# 422 error response
|
|
95
|
+
# @return [Hash] The error response
|
|
96
|
+
def api_error
|
|
97
|
+
{ code: :unprocessable_entity, message: message }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Error class for validation errors
|
|
102
|
+
# @param message [String] The error message
|
|
103
|
+
# @return [ValidationError] The error instance
|
|
104
|
+
class ValidationError < UnprocessableEntityError
|
|
105
|
+
DEFAULT_MESSAGE = "Validation failed"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Error class for internal server errors
|
|
109
|
+
# @param message [String] The error message
|
|
110
|
+
# @return [InternalServerError] The error instance
|
|
111
|
+
class InternalServerError < ServiceError
|
|
112
|
+
DEFAULT_MESSAGE = "Internal server error"
|
|
113
|
+
|
|
114
|
+
# 500 error response
|
|
115
|
+
# @return [Hash] The error response
|
|
116
|
+
def api_error
|
|
117
|
+
{ code: :internal_server_error, message: message }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Error class for service unavailable errors
|
|
122
|
+
# @param message [String] The error message
|
|
123
|
+
# @return [ServiceUnavailableError] The error instance
|
|
124
|
+
class ServiceUnavailableError < ServiceError
|
|
125
|
+
DEFAULT_MESSAGE = "Service unavailable"
|
|
126
|
+
|
|
127
|
+
# 503 error response
|
|
128
|
+
# @return [Hash] The error response
|
|
129
|
+
def api_error
|
|
130
|
+
{ code: :service_unavailable, message: message }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module Servus
|
|
6
|
+
module Support
|
|
7
|
+
class Logger
|
|
8
|
+
# Returns the logger instance depending on the environment
|
|
9
|
+
#
|
|
10
|
+
# @return [Logger] The logger instance
|
|
11
|
+
def self.logger
|
|
12
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
13
|
+
Rails.logger
|
|
14
|
+
else
|
|
15
|
+
@logger ||= ::Logger.new($stdout)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Logs a call to a service
|
|
20
|
+
#
|
|
21
|
+
# @param service_class [Class] The service class
|
|
22
|
+
# @param args [Hash] The arguments passed to the service
|
|
23
|
+
def self.log_call(service_class, args)
|
|
24
|
+
logger.info("Calling #{service_class.name} with args: #{args.inspect}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Logs a result from a service
|
|
28
|
+
#
|
|
29
|
+
# @param service_class [Class] The service class
|
|
30
|
+
# @param result [Servus::Support::Response] The result from the service
|
|
31
|
+
# @param duration [Float] The duration of the service call
|
|
32
|
+
def self.log_result(service_class, result, duration)
|
|
33
|
+
if result.success?
|
|
34
|
+
log_success(service_class, duration)
|
|
35
|
+
else
|
|
36
|
+
log_failure(service_class, result.error, duration)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Logs a successful result from a service
|
|
41
|
+
#
|
|
42
|
+
# @param service_class [Class] The service class
|
|
43
|
+
# @param duration [Float] The duration of the service call
|
|
44
|
+
def self.log_success(service_class, duration)
|
|
45
|
+
logger.info("#{service_class.name} succeeded in #{duration.round(3)}s")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Logs a failed result from a service
|
|
49
|
+
#
|
|
50
|
+
# @param service_class [Class] The service class
|
|
51
|
+
# @param error [Servus::Support::Errors::ServiceError] The error from the service
|
|
52
|
+
# @param duration [Float] The duration of the service call
|
|
53
|
+
def self.log_failure(service_class, error, duration)
|
|
54
|
+
logger.warn("#{service_class.name} failed in #{duration.round(3)}s with error: #{error}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Logs a validation error from a service
|
|
58
|
+
#
|
|
59
|
+
# @param service_class [Class] The service class
|
|
60
|
+
# @param error [Servus::Support::Errors::ValidationError] The validation error
|
|
61
|
+
def self.log_validation_error(service_class, error)
|
|
62
|
+
logger.error("#{service_class.name} validation error: #{error.message}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Logs an uncaught exception from a service
|
|
66
|
+
#
|
|
67
|
+
# @param service_class [Class] The service class
|
|
68
|
+
# @param exception [Exception] The uncaught exception
|
|
69
|
+
def self.log_exception(service_class, exception)
|
|
70
|
+
logger.error("#{service_class.name} uncaught exception: #{exception.class} - #{exception.message}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Support
|
|
5
|
+
class Response
|
|
6
|
+
attr_reader :data, :error
|
|
7
|
+
|
|
8
|
+
def initialize(success, data, error)
|
|
9
|
+
@success = success
|
|
10
|
+
@data = data
|
|
11
|
+
@error = error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def success?
|
|
15
|
+
@success
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Support
|
|
5
|
+
class Validator
|
|
6
|
+
# Class-level schema cache
|
|
7
|
+
@schema_cache = {}
|
|
8
|
+
|
|
9
|
+
# Validate service arguments against schema
|
|
10
|
+
def self.validate_arguments!(service_class, args)
|
|
11
|
+
schema = load_schema(service_class, "arguments")
|
|
12
|
+
return true unless schema # Skip validation if no schema exists
|
|
13
|
+
|
|
14
|
+
serialized_result = args.as_json
|
|
15
|
+
validation_errors = JSON::Validator.fully_validate(schema, serialized_result)
|
|
16
|
+
|
|
17
|
+
if validation_errors.any?
|
|
18
|
+
error_message = "Invalid arguments for #{service_class.name}: #{validation_errors.join(", ")}"
|
|
19
|
+
raise Servus::Base::ValidationError, error_message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Validate service result against schema
|
|
26
|
+
def self.validate_result!(service_class, result)
|
|
27
|
+
return result unless result.success?
|
|
28
|
+
|
|
29
|
+
schema = load_schema(service_class, "result")
|
|
30
|
+
return result unless schema # Skip validation if no schema exists
|
|
31
|
+
|
|
32
|
+
serialized_result = result.data.as_json
|
|
33
|
+
validation_errors = JSON::Validator.fully_validate(schema, serialized_result)
|
|
34
|
+
|
|
35
|
+
if validation_errors.any?
|
|
36
|
+
error_message = "Invalid result structure from #{service_class.name}: #{validation_errors.join(", ")}"
|
|
37
|
+
raise Servus::Base::ValidationError, error_message
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Load schema from file with caching
|
|
44
|
+
def self.load_schema(service_class, type)
|
|
45
|
+
# Get service path based on class name (e.g., "process_payment" from "Servus::ProcessPayment::Service")
|
|
46
|
+
service_namespace = service_class.name.split("::")[..-2].map do |s|
|
|
47
|
+
s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
48
|
+
end.join("/")
|
|
49
|
+
schema_path = Servus.config.schema_path_for(service_namespace, type)
|
|
50
|
+
|
|
51
|
+
# Return from cache if available
|
|
52
|
+
return @schema_cache[schema_path] if @schema_cache.key?(schema_path)
|
|
53
|
+
|
|
54
|
+
inline_schema_constant_name = "#{service_class}::#{type.upcase}_SCHEMA"
|
|
55
|
+
inline_schema_constant = Object.const_defined?(inline_schema_constant_name) ? Object.const_get(inline_schema_constant_name) : nil
|
|
56
|
+
|
|
57
|
+
if inline_schema_constant
|
|
58
|
+
@schema_cache[schema_path] =
|
|
59
|
+
inline_schema_constant.respond_to?(:deep_stringify_keys) ? inline_schema_constant.deep_stringify_keys : inline_schema_constant
|
|
60
|
+
elsif File.exist?(schema_path)
|
|
61
|
+
@schema_cache[schema_path] = JSON.parse(File.read(schema_path))
|
|
62
|
+
else
|
|
63
|
+
# Cache nil result to avoid checking file system again
|
|
64
|
+
@schema_cache[schema_path] = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@schema_cache[schema_path]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Clear the schema cache (useful for testing or development)
|
|
71
|
+
def self.clear_cache!
|
|
72
|
+
@schema_cache = {}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the schema cache
|
|
76
|
+
def self.cache
|
|
77
|
+
@schema_cache
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/servus.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Globals
|
|
4
|
+
require "json-schema"
|
|
5
|
+
require "active_model_serializers"
|
|
6
|
+
|
|
7
|
+
# Servus namespace
|
|
8
|
+
module Servus; end
|
|
9
|
+
|
|
10
|
+
# Railtie
|
|
11
|
+
require_relative "servus/railtie" if defined?(Rails::Railtie)
|
|
12
|
+
|
|
13
|
+
# Config
|
|
14
|
+
require_relative "servus/config"
|
|
15
|
+
|
|
16
|
+
# Support
|
|
17
|
+
require_relative "servus/support/logger"
|
|
18
|
+
require_relative "servus/support/response"
|
|
19
|
+
require_relative "servus/support/validator"
|
|
20
|
+
require_relative "servus/support/errors"
|
|
21
|
+
|
|
22
|
+
# Core
|
|
23
|
+
require_relative "servus/version"
|
|
24
|
+
require_relative "servus/base"
|
data/sig/servus.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: servus
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sebastian Scholl
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: active_model_serializers
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json-schema
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: A gem for managing service objects.
|
|
41
|
+
email:
|
|
42
|
+
- sebscholl@gmail.com
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- ".rspec"
|
|
48
|
+
- CHANGELOG.md
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- READme.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- lib/generators/servus/service/service_generator.rb
|
|
53
|
+
- lib/generators/servus/service/templates/arguments.json.erb
|
|
54
|
+
- lib/generators/servus/service/templates/result.json.erb
|
|
55
|
+
- lib/generators/servus/service/templates/service.rb.erb
|
|
56
|
+
- lib/generators/servus/service/templates/service_spec.rb.erb
|
|
57
|
+
- lib/servus.rb
|
|
58
|
+
- lib/servus/base.rb
|
|
59
|
+
- lib/servus/config.rb
|
|
60
|
+
- lib/servus/railtie.rb
|
|
61
|
+
- lib/servus/support/errors.rb
|
|
62
|
+
- lib/servus/support/logger.rb
|
|
63
|
+
- lib/servus/support/response.rb
|
|
64
|
+
- lib/servus/support/validator.rb
|
|
65
|
+
- lib/servus/version.rb
|
|
66
|
+
- sig/servus.rbs
|
|
67
|
+
homepage: https://github.com/zarpay/servus
|
|
68
|
+
licenses:
|
|
69
|
+
- MIT
|
|
70
|
+
metadata:
|
|
71
|
+
allowed_push_host: https://rubygems.org
|
|
72
|
+
homepage_uri: https://github.com/zarpay/servus
|
|
73
|
+
source_code_uri: https://github.com/zarpay/servus
|
|
74
|
+
changelog_uri: https://github.com/zarpay/servus/blob/main/CHANGELOG.md
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 3.0.0
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 3.6.7
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: A gem for managing service objects.
|
|
92
|
+
test_files: []
|