Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ rails generate light_services:install
```ruby
class GreetService < Light::Services::Base
# Arguments
arg :name
arg :age
arg :name, type: String
arg :age, type: Integer

# Steps
step :build_message
step :send_message

# Outputs
output :message
output :message, type: String

private

Expand All @@ -58,14 +58,14 @@ class GreetService < Light::Services::Base
end
```

## Advanced Example
## Advanced Example (with dry-types)

```ruby
class User::ResetPassword < Light::Services::Base
# Arguments
arg :user, type: User, optional: true
arg :email, type: String, optional: true
arg :send_email, type: [TrueClass, FalseClass], default: true
# Arguments with dry-types for advanced validation
arg :user, type: Types::Instance(User).optional, optional: true
arg :email, type: Types::String.constrained(format: /@/), optional: true
arg :send_email, type: Types::Bool, default: true

# Steps
step :validate
Expand All @@ -75,8 +75,8 @@ class User::ResetPassword < Light::Services::Base
step :send_reset_email, if: :send_email?

# Outputs
output :user, type: User
output :reset_token, type: String
output :user, type: Types::Instance(User)
output :reset_token, type: Types::Strict::String

private

Expand Down
4 changes: 4 additions & 0 deletions docs/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class HappyBirthdayService < ApplicationService
end
```

{% hint style="info" %}
You can enforce type definitions for all arguments by enabling `require_type` in your configuration. See [Configuration](configuration.md#enforcing-type-definitions) for details.
{% endhint %}

You can specify multiple allowed types using an array.

```ruby
Expand Down
34 changes: 34 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Light::Services.configure do |config|
config.break_on_warning = false # Stop step execution when a warning is added
config.raise_on_warning = false # Raise an exception when a warning is added
config.rollback_on_warning = false # Rollback transaction when a warning is added

# Type enforcement
config.require_type = true # Require type option for all arguments and outputs
end
```

Expand All @@ -38,6 +41,7 @@ end
| `break_on_warning` | `false` | Stops executing remaining steps when a warning is added |
| `raise_on_warning` | `false` | Raises `Light::Services::Error` when a warning is added |
| `rollback_on_warning` | `false` | Rolls back the transaction when a warning is added |
| `require_type` | `true` | Raises `Light::Services::MissingTypeError` when arguments or outputs are defined without a `type` option |

## Per-Service Configuration

Expand Down Expand Up @@ -160,6 +164,36 @@ end
When `use_transactions` is `true`, Light Services uses `ActiveRecord::Base.transaction(requires_new: true)` to create savepoints, allowing nested services to rollback independently.
{% endhint %}

## Enforcing Type Definitions

By default, `require_type` is enabled to ensure all arguments and outputs have explicit type definitions. This helps catch missing types early and improves code quality.

Defining an argument or output without a type will raise `Light::Services::MissingTypeError`:

```ruby
class MyService < ApplicationService
arg :name # Raises MissingTypeError!
arg :name, type: String # OK

output :result # Raises MissingTypeError!
output :result, type: Hash # OK
end
```

### Disabling Type Enforcement

For legacy projects or gradual migration, you can disable type enforcement:

```ruby
Light::Services.configure do |config|
config.require_type = false
end
```

{% hint style="warning" %}
Disabling `require_type` is not recommended. Explicit types improve code quality and catch errors early.
{% endhint %}

## What's Next?

Now that you understand configuration, learn about the core concepts:
Expand Down
4 changes: 4 additions & 0 deletions docs/outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class AI::Chat < ApplicationService
end
```

{% hint style="info" %}
You can enforce type definitions for all outputs by enabling `require_type` in your configuration. See [Configuration](configuration.md#enforcing-type-definitions) for details.
{% endhint %}

You can specify multiple allowed types using an array.

```ruby
Expand Down
2 changes: 2 additions & 0 deletions lib/light/services/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Config
break_on_warning: false,
raise_on_warning: false,
rollback_on_warning: false,

require_type: true,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Default value contradicts PR's backwards compatibility intent

The PR description explicitly states that require_type should "default to false for backwards compatibility," but the implementation sets require_type: true as the default. This would break existing services that define arguments or outputs without type annotations, causing them to raise MissingTypeError upon upgrading. The documentation also incorrectly shows true as the default.

Additional Locations (1)

Fix in Cursor Fix in Web

}.freeze

attr_accessor(*DEFAULTS.keys)
Expand Down
1 change: 1 addition & 0 deletions lib/light/services/dsl/arguments_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def arg(name, opts = {})
Validation.validate_symbol_name!(name, :argument, self)
Validation.validate_reserved_name!(name, :argument, self)
Validation.validate_name_conflicts!(name, :argument, self)
Validation.validate_type_presence!(name, :argument, self, opts)

own_arguments[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::ARGUMENT))
@arguments = nil # Clear memoized arguments since we're modifying them
Expand Down
1 change: 1 addition & 0 deletions lib/light/services/dsl/outputs_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def output(name, opts = {})
Validation.validate_symbol_name!(name, :output, self)
Validation.validate_reserved_name!(name, :output, self)
Validation.validate_name_conflicts!(name, :output, self)
Validation.validate_type_presence!(name, :output, self, opts)

own_outputs[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::OUTPUT))
@outputs = nil # Clear memoized outputs since we're modifying them
Expand Down
15 changes: 15 additions & 0 deletions lib/light/services/dsl/validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ def self.has_step?(name_sym, service_class)
# Check inherited steps
(service_class.superclass.respond_to?(:steps) && service_class.superclass.steps.key?(name_sym))
end

# Validate that a type is provided when require_type config is enabled
#
# @param name [Symbol] the field name
# @param field_type [Symbol] the type of field (:argument, :output)
# @param service_class [Class] the service class for error messages
# @param opts [Hash] the options hash to check for :type key
def self.validate_type_presence!(name, field_type, service_class, opts)
return unless Light::Services.config.require_type
return if opts.key?(:type)

raise Light::Services::MissingTypeError,
"#{field_type.to_s.capitalize} `#{name}` in #{service_class} " \
"must have a type specified (require_type is enabled)"
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/light/services/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ArgTypeError < Error; end
class ReservedNameError < Error; end
class InvalidNameError < Error; end
class NoStepsError < Error; end
class MissingTypeError < Error; end

# Backwards compatibility aliases (deprecated)
NoStepError = Error
Expand Down
8 changes: 8 additions & 0 deletions spec/light/services/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,15 @@
break_on_warning: false,
raise_on_warning: false,
rollback_on_warning: false,
require_type: true,
})
end
end

describe "require_type accessor" do
it "has accessor for require_type" do
config.require_type = true
expect(config.require_type).to be(true)
end
end
end
Loading
Loading