lex-transformer 0.1.3 → 0.2.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +16 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +36 -19
  5. data/CHANGELOG.md +35 -0
  6. data/CLAUDE.md +79 -0
  7. data/Dockerfile +9 -0
  8. data/Gemfile +13 -1
  9. data/README.md +121 -19
  10. data/Rakefile +2 -0
  11. data/docker_deploy.rb +14 -0
  12. data/lex-transformer.gemspec +15 -21
  13. data/lib/legion/extensions/transformer/actors/transform.rb +20 -14
  14. data/lib/legion/extensions/transformer/client.rb +64 -0
  15. data/lib/legion/extensions/transformer/engines/base.rb +19 -0
  16. data/lib/legion/extensions/transformer/engines/erb.rb +37 -0
  17. data/lib/legion/extensions/transformer/engines/jsonpath.rb +41 -0
  18. data/lib/legion/extensions/transformer/engines/liquid.rb +31 -0
  19. data/lib/legion/extensions/transformer/engines/llm.rb +36 -0
  20. data/lib/legion/extensions/transformer/engines/registry.rb +51 -0
  21. data/lib/legion/extensions/transformer/engines/static.rb +21 -0
  22. data/lib/legion/extensions/transformer/helpers/schema_validator.rb +48 -0
  23. data/lib/legion/extensions/transformer/runners/transform.rb +73 -50
  24. data/lib/legion/extensions/transformer/transport/exchanges/task.rb +10 -4
  25. data/lib/legion/extensions/transformer/transport/messages/message.rb +27 -17
  26. data/lib/legion/extensions/transformer/transport/queues/transform.rb +12 -6
  27. data/lib/legion/extensions/transformer/transport.rb +19 -13
  28. data/lib/legion/extensions/transformer/version.rb +3 -1
  29. data/lib/legion/extensions/transformer.rb +8 -2
  30. metadata +31 -63
  31. data/.circleci/config.yml +0 -61
  32. data/CODE_OF_CONDUCT.md +0 -74
  33. data/Gemfile.lock +0 -37
  34. data/bin/console +0 -14
  35. data/bin/setup +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc75a07d6813a3629e479db7d5d5332df5732774ccd67cb2409511fb081b3030
4
- data.tar.gz: 8f1a7eb3b7e78458b1cd435b813bc5a1c71e877357eff8808e93bb909550a830
3
+ metadata.gz: d7f4f19cb531c6f7cb50302bb3f2aab973b55976bb05d1b870ac2ad4f7bdb9a0
4
+ data.tar.gz: a445f592b23aae48a5510a7c6f17ee3cf0ea0a29dd3c9d67bc405b5b208a0c72
5
5
  SHA512:
6
- metadata.gz: b58d6e24f6e205e7a9f5a5317e3260709c288066916864cc215f4c788a9688b69023678f269412d5d83ecfae5916b5d5b81ab48b97116f0fa0174cb16060738d
7
- data.tar.gz: d2bef037e4c5e5ae863b5dc066021d2e1ea92063f19c175239e69f25e59acb6bfc7e30a3868a9d188b7fe4ebfe686f022778e532f6c0624f859a92ad014aa25c
6
+ metadata.gz: 21096cee7c407f8df2aa5307e3fe71e35008103a798bc7f9c9cd8dfd1724728af7d65fbeeada8b5df657c412bdcfd44c60fd100efb2a3f63815c6615e63f9917
7
+ data.tar.gz: 4e6348dc31ae9217b34040010ecf8b613b8b0e97ae1995854a3f30c821bce8a5b6d190bb283ad9a06e305fe128b68bd8a3533772451d608c55912659c1970ec9
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ Gemfile.lock
data/.rubocop.yml CHANGED
@@ -1,32 +1,49 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
1
6
  Layout/LineLength:
2
- Max: 120
7
+ Max: 160
8
+ Layout/SpaceAroundEqualsInParameterDefault:
9
+ EnforcedStyle: space
10
+ Layout/HashAlignment:
11
+ EnforcedHashRocketStyle: table
12
+ EnforcedColonStyle: table
13
+
3
14
  Metrics/MethodLength:
4
15
  Max: 50
5
16
  Metrics/ClassLength:
6
17
  Max: 1500
18
+ Metrics/ModuleLength:
19
+ Max: 1500
7
20
  Metrics/BlockLength:
8
- Max: 50
9
- Metrics/PerceivedComplexity:
10
- Max: 20
21
+ Max: 40
22
+ Exclude:
23
+ - 'spec/**/*'
24
+ Metrics/AbcSize:
25
+ Max: 60
11
26
  Metrics/CyclomaticComplexity:
12
27
  Max: 15
13
- Metrics/AbcSize:
14
- Max: 50
15
- Layout/SpaceAroundEqualsInParameterDefault:
16
- EnforcedStyle: space
17
- Style/SymbolArray:
18
- Enabled: true
19
- Layout/HashAlignment:
20
- EnforcedHashRocketStyle: table
21
- EnforcedColonStyle: table
28
+ Metrics/PerceivedComplexity:
29
+ Max: 17
30
+ Metrics/ParameterLists:
31
+ Enabled: false
32
+
22
33
  Style/Documentation:
23
34
  Enabled: false
24
- AllCops:
25
- TargetRubyVersion: 2.5
26
- NewCops: enable
35
+ Style/SymbolArray:
36
+ Enabled: true
27
37
  Style/FrozenStringLiteralComment:
28
- Enabled: false
38
+ Enabled: true
39
+ EnforcedStyle: always
40
+
29
41
  Naming/FileName:
30
42
  Enabled: false
31
- Style/ClassAndModuleChildren:
32
- Enabled: false
43
+ Naming/PredicateMethod:
44
+ Enabled: false
45
+ Naming/PredicatePrefix:
46
+ Enabled: false
47
+
48
+ Gemspec/DevelopmentDependencies:
49
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## [0.2.1] - 2026-03-17
4
+
5
+ ### Added
6
+ - LLM transform engine (`engines/llm.rb`) — provider-agnostic natural language transformation via `Legion::LLM.chat`
7
+ - Registered as `:llm` in engine registry (explicit only, no auto-detection)
8
+
9
+ ## [0.2.0] - 2026-03-17
10
+
11
+ ### Added
12
+ - Pluggable engine system with abstract `Engines::Base` and `Engines::Registry`
13
+ - 4 template engines: ERB (extracted from runner), Static (JSON passthrough), Liquid, JSONPath (dot-notation extraction)
14
+ - `Engines::Registry.detect` auto-selects ERB or static engine based on template content
15
+ - `render_transformation` accepts optional `engine:` keyword to force a specific engine
16
+ - `Helpers::SchemaValidator` validates transformed output against declared schemas (required keys, type checks)
17
+ - `transform` accepts optional `schema:` parameter for post-render validation
18
+ - `transform_chain` method for sequential multi-step pipelines with per-step engine selection and schema validation
19
+ - Standalone `Client` class for framework-independent usage with `transform` and `transform_chain` methods
20
+ - SimpleCov coverage reporting
21
+ - Modern packaging: grouped test dependencies, `require_relative` in gemspec, `rubocop-rspec`
22
+
23
+ ### Fixed
24
+ - `dispatch_multiplied` payload mutation bug: `new_payload = payload` replaced with `new_payload = payload.dup`
25
+ - Spec filename typo: `tranformer_spec.rb` renamed to `transformer_spec.rb`
26
+
27
+ ### Changed
28
+ - ERB rendering logic extracted from runner into `Engines::Erb` class
29
+ - `build_template_variables` moved to `Engines::Erb` as private `build_variables`
30
+ - Runner no longer directly requires `tilt` (delegated to `Engines::Erb`)
31
+
32
+ ## [0.1.4] - 2026-03-13
33
+
34
+ ### Added
35
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,79 @@
1
+ # lex-transformer: Payload Transformation Engine for LegionIO
2
+
3
+ **Repository Level 3 Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-core/CLAUDE.md`
5
+ - **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Legion Extension that transforms task payloads between services in a relationship chain. Uses ERB template-based transformation (via the `tilt` gem) to map data from one task's output into the format expected by the next task's input. Supports single-hash output (1:1 dispatch) and array output (fan-out/multiply). Requires `legion-data` (`data_required? true`).
10
+
11
+ **GitHub**: https://github.com/LegionIO/lex-transformer
12
+ **License**: MIT
13
+ **Version**: 0.2.1
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ Legion::Extensions::Transformer
19
+ ├── Actors/
20
+ │ └── Transform # Subscription actor consuming transform requests
21
+ ├── Runners/
22
+ │ └── Transform # Executes template-based payload transformation
23
+ │ ├── transform # Entry point: render + dispatch
24
+ │ ├── render_transformation # ERB rendering via tilt, or plain JSON if no ERB tags
25
+ │ ├── build_template_variables # Inject crypt/settings/cache/task into ERB scope
26
+ │ ├── dispatch_transformed # Route Hash (single) or Array (fan-out) results
27
+ │ ├── dispatch_multiplied # Fan-out: create a new task per array element
28
+ │ └── send_task # Publish transformed payload to next runner
29
+ └── Transport/
30
+ ├── Exchanges/Task # Publishes to the task exchange
31
+ ├── Queues/Transform # Subscribes to transformation queue
32
+ └── Messages/Message # Transform request message format
33
+ ```
34
+
35
+ ## Key Files
36
+
37
+ | Path | Purpose |
38
+ |------|---------|
39
+ | `lib/legion/extensions/transformer.rb` | Entry point (`data_required? true`) |
40
+ | `lib/legion/extensions/transformer/runners/transform.rb` | Core transformation logic |
41
+ | `lib/legion/extensions/transformer/actors/transform.rb` | AMQP subscription actor |
42
+ | `lib/legion/extensions/transformer/transport.rb` | Transport setup |
43
+
44
+ ## Template Variables Available in ERB
45
+
46
+ | Variable | Available when |
47
+ |----------|---------------|
48
+ | All payload keys | Always (splatted into template scope) |
49
+ | `crypt` | Template string contains `'crypt'` |
50
+ | `settings` | Template string contains `'settings'` |
51
+ | `cache` | Template string contains `'cache'` |
52
+ | `task` | Template string contains `'task'` and payload has `task_id` |
53
+
54
+ ## Dispatch Behavior
55
+
56
+ - **Hash result**: Updates task to `transformer.succeeded`, dispatches single task, updates to `task.queued`
57
+ - **Array result**: Fan-out via `dispatch_multiplied` - creates a new task record per element, dispatches each, marks original as `task.multiplied`
58
+ - **Plain JSON** (no ERB tags): Parsed directly without template rendering
59
+
60
+ ## Dependencies
61
+
62
+ | Gem | Purpose |
63
+ |-----|---------|
64
+ | `tilt` (>= 2.3) | Template engine abstraction for ERB rendering |
65
+ | `legion-data` | Required - task record creation for fan-out |
66
+
67
+ ## Testing
68
+
69
+ ```bash
70
+ bundle install
71
+ bundle exec rspec
72
+ bundle exec rubocop
73
+ ```
74
+
75
+ Spec files: `spec/legion/extensions/tranformer_spec.rb` (note: typo in filename), `spec/legion/extensions/transform_runner_spec.rb`
76
+
77
+ ---
78
+
79
+ **Maintained By**: Matthew Iverson (@Esity)
data/Dockerfile ADDED
@@ -0,0 +1,9 @@
1
+ FROM legionio/legion:latest
2
+ LABEL maintainer="Matthew Iverson <matthewdiverson@gmail.com>"
3
+
4
+ RUN mkdir /etc/legionio
5
+ RUN apk update && apk add build-base tzdata gcc git
6
+
7
+ COPY . ./
8
+ RUN gem install lex-transformer legion-data --no-document --no-prerelease
9
+ CMD ruby --yjit $(which legionio)
data/Gemfile CHANGED
@@ -1,4 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- # Specify your gem's dependencies in legion-extensions-transformer.gemspec
4
5
  gemspec
6
+
7
+ group :test do
8
+ gem 'base64'
9
+ gem 'liquid', '~> 5.0'
10
+ gem 'rake'
11
+ gem 'rspec', '~> 3.13'
12
+ gem 'rspec_junit_formatter'
13
+ gem 'rubocop', '~> 1.75'
14
+ gem 'rubocop-rspec'
15
+ gem 'simplecov'
16
+ end
data/README.md CHANGED
@@ -1,38 +1,140 @@
1
- # Legion::Extensions::Transformer
1
+ # lex-transformer
2
2
 
3
- Used to transform data to match the action for a relationship
3
+ Payload transformation engine for [LegionIO](https://github.com/LegionIO/LegionIO). Transforms task payloads between services in a relationship chain using pluggable template engines. Maps data from one task's output into the format expected by the next task's input.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ ```bash
8
+ gem install lex-transformer
9
+ ```
10
+
11
+ Or add to your Gemfile:
8
12
 
9
13
  ```ruby
10
14
  gem 'lex-transformer'
11
15
  ```
12
16
 
13
- And then execute:
17
+ ## Standalone Client
18
+
19
+ Use the transformer without the full LegionIO framework:
20
+
21
+ ```ruby
22
+ require 'legion/extensions/transformer/client'
23
+
24
+ client = Legion::Extensions::Transformer::Client.new
25
+
26
+ # Single transform
27
+ result = client.transform(
28
+ transformation: '{"greeting":"hello <%= name %>"}',
29
+ payload: { name: 'world' }
30
+ )
31
+ result[:success] # => true
32
+ result[:result] # => { greeting: "hello world" }
33
+
34
+ # With explicit engine
35
+ result = client.transform(
36
+ transformation: '{"greeting":"hello {{ name }}"}',
37
+ payload: { name: 'world' },
38
+ engine: :liquid
39
+ )
40
+
41
+ # With schema validation
42
+ result = client.transform(
43
+ transformation: '{"name":"test"}',
44
+ payload: {},
45
+ schema: { required_keys: [:name, :email] }
46
+ )
47
+ result[:success] # => false
48
+ result[:status] # => "transformer.validation_failed"
49
+ result[:errors] # => ["missing required key: email"]
50
+ ```
51
+
52
+ ### Transform Chains
53
+
54
+ Pipe data through sequential transformation steps:
55
+
56
+ ```ruby
57
+ result = client.transform_chain(
58
+ steps: [
59
+ { transformation: '{"user":"<%= login %>"}', schema: { required_keys: [:user] } },
60
+ { transformation: '{"message":"Welcome, <%= user %>!"}' }
61
+ ],
62
+ payload: { login: 'alice' }
63
+ )
64
+ result[:success] # => true
65
+ result[:result][:args] # => { message: "Welcome, alice!" }
66
+ ```
67
+
68
+ Each step's output merges into the running payload, so subsequent steps can reference keys from earlier steps.
69
+
70
+ ## Engines
71
+
72
+ | Engine | Name | Detection | Description |
73
+ |--------|------|-----------|-------------|
74
+ | ERB | `:erb` | `<%` or `%>` in template | Full ERB template rendering via `tilt` |
75
+ | Static | `:static` | Default (no ERB markers) | Plain JSON passthrough |
76
+ | Liquid | `:liquid` | Explicit only | Liquid template rendering (`{{ var }}`) |
77
+ | JSONPath | `:jsonpath` | Explicit only | Dot-notation value extraction from payload |
78
+ | LLM | `:llm` | Explicit only | Natural language transformation via Legion::LLM |
14
79
 
15
- $ bundle
80
+ Auto-detection selects ERB when the template contains ERB markers, otherwise falls back to Static. Use the `engine:` parameter to force a specific engine.
16
81
 
17
- Or install it yourself as:
82
+ ### LLM Engine
18
83
 
19
- $ gem install lex-transformer
84
+ The LLM engine uses natural language instructions as the "template":
20
85
 
21
- ## Adding to Legion
22
- You can manually install with a `gem install lex-transformer` command or by adding it into your settings with something like this
23
- ```json
24
- {
25
- "extensions": {
26
- "transformer": {
27
- "enabled": true, "workers": 1
28
- }
29
- }
86
+ ```ruby
87
+ client.transform(
88
+ transformation: "Summarize this webhook payload into a Slack notification with the PR title and author",
89
+ payload: { action: "opened", pull_request: { title: "Fix auth", user: { login: "alice" } } },
90
+ engine: :llm
91
+ )
92
+ ```
93
+
94
+ Requires `legion-llm` to be started. Provider-agnostic — uses whatever LLM provider is configured (Ollama, Bedrock, Anthropic, OpenAI, Gemini).
95
+
96
+ ## Schema Validation
97
+
98
+ Validate transformed output against a declared schema:
99
+
100
+ ```ruby
101
+ schema = {
102
+ required_keys: [:name, :email], # keys that must be present
103
+ types: { name: String, email: String, age: Integer } # optional type checks
30
104
  }
31
105
  ```
32
106
 
33
- ## Usage
34
- *to be added*
107
+ When validation fails, the transform returns `{ success: false, status: 'transformer.validation_failed', errors: [...] }`.
108
+
109
+ ## Runners
110
+
111
+ ### Transform
112
+
113
+ #### `transform(transformation:, engine: nil, schema: nil, **payload)`
114
+
115
+ Renders the transformation template against the payload, optionally validates the result, then dispatches:
116
+
117
+ - Hash result: routes to next task (`task.queued`)
118
+ - Array result: fan-out via `dispatch_multiplied` (one task per element, marked `task.multiplied`)
119
+ - Schema failure: `transformer.validation_failed`, no dispatch
120
+
121
+ #### `transform_chain(steps:, **payload)`
122
+
123
+ Sequential pipeline. Each step has `:transformation`, optional `:engine`, optional `:schema`. Output of step N feeds into step N+1. Stops on first schema failure.
124
+
125
+ ## Transport
126
+
127
+ - **Exchange**: `task` (inherits from `Legion::Transport::Exchanges::Task`)
128
+ - **Queue**: `task.transform`
129
+ - **Routing keys**: `task.subtask`, `task.subtask.transform`
130
+
131
+ ## Requirements
132
+
133
+ - Ruby >= 3.4
134
+ - [LegionIO](https://github.com/LegionIO/LegionIO) framework (for AMQP actor mode)
135
+ - `tilt` >= 2.3
136
+ - Standalone Client works without the framework
35
137
 
36
138
  ## License
37
139
 
38
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
140
+ MIT
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
data/docker_deploy.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ name = 'transformer'
5
+ require "./lib/legion/extensions/#{name}/version"
6
+ version = Legion::Extensions::Transformer::VERSION
7
+
8
+ puts "Building docker image for Legion v#{version}"
9
+ system("docker build --tag legionio/lex-#{name}:v#{version} .")
10
+ puts 'Pushing to hub.docker.com'
11
+ system("docker push legionio/lex-#{name}:v#{version}")
12
+ system("docker tag legionio/lex-#{name}:v#{version} legionio/lex-#{name}:latest")
13
+ system("docker push legionio/lex-#{name}:latest")
14
+ puts 'completed'
@@ -1,36 +1,30 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'legion/extensions/transformer/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/transformer/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'lex-transformer'
7
7
  spec.version = Legion::Extensions::Transformer::VERSION
8
- spec.authors = ['Miverson']
8
+ spec.authors = ['Esity']
9
9
  spec.email = ['matthewdiverson@gmail.com']
10
10
 
11
- spec.summary = 'LEX-Transformer is used to transform payloads for the output'
12
- spec.description = 'Runs transformer statements against tasks in a relationship'
13
- spec.homepage = 'https://bitbucket.org/legion-io/lex-transformer'
11
+ spec.summary = 'Payload transformation engine for LegionIO task chains'
12
+ spec.description = 'Template-based payload transformation between tasks with multiple engines, schema validation, and composition'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-transformer'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
15
+ spec.required_ruby_version = '>= 3.4'
16
16
 
17
- if spec.respond_to?(:metadata)
18
- spec.metadata['homepage_uri'] = spec.homepage
19
- spec.metadata['source_code_uri'] = 'https://bitbucket.org/legion-io/lex-transformer'
20
- spec.metadata['changelog_uri'] = 'https://bitbucket.org/legion-io/lex-transformer'
21
- else
22
- raise 'RubyGems 2.0 or newer is required to protect against ' \
23
- 'public gem pushes.'
24
- end
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-transformer'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-transformer'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-transformer/blob/main/CHANGELOG.md'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-transformer/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
23
 
26
24
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
25
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
26
  end
29
27
  spec.require_paths = ['lib']
30
28
 
31
- spec.add_dependency 'tilt'
32
-
33
- spec.add_development_dependency 'bundler'
34
- spec.add_development_dependency 'rake'
35
- spec.add_development_dependency 'rspec'
29
+ spec.add_dependency 'tilt', '>= 2.3'
36
30
  end
@@ -1,20 +1,26 @@
1
- module Legion::Extensions::Transformer
2
- module Actor
3
- class Transform < Legion::Extensions::Actors::Subscription
4
- def runner_function
5
- 'transform'
6
- end
1
+ # frozen_string_literal: true
7
2
 
8
- def check_subtask?
9
- false
10
- end
3
+ module Legion
4
+ module Extensions
5
+ module Transformer
6
+ module Actor
7
+ class Transform < Legion::Extensions::Actors::Subscription
8
+ def runner_function
9
+ 'transform'
10
+ end
11
11
 
12
- def generate_task?
13
- false
14
- end
12
+ def check_subtask?
13
+ false
14
+ end
15
+
16
+ def generate_task?
17
+ false
18
+ end
15
19
 
16
- def use_runner?
17
- false
20
+ def use_runner?
21
+ false
22
+ end
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'engines/registry'
4
+ require_relative 'helpers/schema_validator'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Transformer
9
+ class Client
10
+ def transform(transformation:, payload:, engine: nil, schema: nil)
11
+ eng = resolve_engine(engine, transformation)
12
+ rendered = eng.render(transformation, payload)
13
+ rendered = parse_rendered(rendered)
14
+
15
+ if schema
16
+ validation = Helpers::SchemaValidator.validate(schema: schema, data: rendered)
17
+ return { success: false, status: 'transformer.validation_failed', errors: validation[:errors] } unless validation[:valid]
18
+ end
19
+
20
+ { success: true, result: rendered }
21
+ end
22
+
23
+ def transform_chain(steps:, payload:)
24
+ result = payload.dup
25
+ steps.each do |step|
26
+ eng = resolve_engine(step[:engine], step[:transformation])
27
+ rendered = eng.render(step[:transformation], result)
28
+ rendered = parse_rendered(rendered)
29
+
30
+ if step[:schema]
31
+ validation = Helpers::SchemaValidator.validate(schema: step[:schema], data: rendered)
32
+ return { success: false, status: 'transformer.validation_failed', errors: validation[:errors] } unless validation[:valid]
33
+ end
34
+
35
+ if rendered.is_a?(Hash)
36
+ result = result.merge({ args: rendered }.merge(rendered))
37
+ else
38
+ result[:args] = rendered
39
+ end
40
+ end
41
+ { success: true, result: result }
42
+ end
43
+
44
+ private
45
+
46
+ def resolve_engine(engine_name, transformation)
47
+ if engine_name
48
+ Engines::Registry.fetch(engine_name)
49
+ else
50
+ Engines::Registry.detect(transformation)
51
+ end
52
+ end
53
+
54
+ def parse_rendered(rendered)
55
+ return rendered unless rendered.is_a?(String)
56
+
57
+ Legion::JSON.load(rendered)
58
+ rescue StandardError
59
+ rendered
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Transformer
6
+ module Engines
7
+ class Base
8
+ def render(template, payload)
9
+ raise NotImplementedError, "#{self.class}#render must be implemented"
10
+ end
11
+
12
+ def name
13
+ raise NotImplementedError, "#{self.class}#name must be implemented"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tilt'
4
+ require_relative 'base'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Transformer
9
+ module Engines
10
+ class Erb < Base
11
+ def name
12
+ :erb
13
+ end
14
+
15
+ def render(template, payload)
16
+ tilt_template = Tilt['erb'].new { template }
17
+ variables = build_variables(template, payload)
18
+ tilt_template.render(Object.new, variables)
19
+ end
20
+
21
+ private
22
+
23
+ def build_variables(template, payload)
24
+ variables = { **payload }
25
+ variables[:crypt] = Legion::Crypt if defined?(Legion::Crypt) && template.include?('crypt')
26
+ variables[:settings] = Legion::Settings if defined?(Legion::Settings) && template.include?('settings')
27
+ variables[:cache] = Legion::Cache if defined?(Legion::Cache) && template.include?('cache')
28
+ if payload.key?(:task_id) && template.include?('task') && defined?(Legion::Data::Model::Task)
29
+ variables[:task] = Legion::Data::Model::Task[payload[:task_id]]
30
+ end
31
+ variables
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end