feat(datasource-pylon): foundation β gem, config & resilient client - #341
Conversation
Story 1 of the Pylon datasource (EXT-5). Adds the forest_admin_datasource_pylon gem skeleton: Zeitwerk autoloading, typed error hierarchy with an APIError carrying HTTP status and parsed body, configurable logger, Configuration with api_key validation, and a Faraday client authenticating with a Bearer token plus a GET /me health check. The Faraday middleware order is deliberate and differs from the Mambu Payments gem: raise_error sits outside the JSON parser so errors carry an already-parsed body, and retry sits innermost so it can observe raw statuses. Behind raise_error the retry middleware never sees a 429 and retry_statuses silently does nothing. Non-idempotent verbs are only retried on 429, where Pylon rejected the request before processing it. Wires the package into the CI lint, test and coverage jobs. The semantic-release publish pipeline is intentionally left untouched until Story 9, so an incomplete gem is never pushed to RubyGems. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 new issues
|
| gem 'rspec', '~> 3.0' | ||
| gem 'simplecov', '~> 0.22', require: false | ||
| gem 'webmock', '~> 3.0' | ||
| end |
| Logger.new($stderr).tap { |l| l.progname = 'forest_admin_datasource_pylon' } | ||
| end | ||
| end | ||
| end |
| join_errors(parsed['errors']) | ||
| message = parsed.to_json if message.to_s.empty? | ||
| message = "#{message} (request_id: #{parsed["request_id"]})" if parsed['request_id'] | ||
| message.to_s[0, 500] |
|
|
||
| attr_reader :api_key, :base_url, :open_timeout, :timeout, :max_retries, :retry_interval | ||
|
|
||
| def initialize(api_key:, base_url: nil, open_timeout: 5, timeout: 30, max_retries: 3, retry_interval: 0.5) |
There was a problem hiding this comment.
π‘ Medium
agent-ruby/.github/workflows/build.yml
Line 133 in 8d94a91
The Send coverage step references reports/3.4-forest_admin_datasource_pylon/coverage.json, but coverage artifacts are uploaded only for ruby-version == '4.0', so the artifact is named 4.0-forest_admin_datasource_pylon. The 3.4 path does not exist, so the Pylon coverage file is missing and the qltysh/qlty-action/coverage step receives a non-existent input. The coverage job matrix uses 3.4, which mismatches the 4.0 upload condition. Either use 4.0 in the files path for Pylon or align the upload and coverage job matrices to the same version.
π Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/build.yml around line 133:
The `Send coverage` step references `reports/3.4-forest_admin_datasource_pylon/coverage.json`, but coverage artifacts are uploaded only for `ruby-version == '4.0'`, so the artifact is named `4.0-forest_admin_datasource_pylon`. The `3.4` path does not exist, so the Pylon coverage file is missing and the `qltysh/qlty-action/coverage` step receives a non-existent input. The coverage job matrix uses `3.4`, which mismatches the `4.0` upload condition. Either use `4.0` in the `files` path for Pylon or align the upload and coverage job matrices to the same version.
| spec.add_dependency 'activesupport', '>= 6.1' | ||
| spec.add_dependency 'faraday', '~> 2.0' |
There was a problem hiding this comment.
π High forest_admin_datasource_pylon/forest_admin_datasource_pylon.gemspec:32
The gemspec omits forest_admin_datasource_toolkit from its runtime dependencies, so installing forest_admin_datasource_pylon does not pull in the toolkit. When users require 'forest_admin_datasource_pylon', the gem fails with LoadError because the toolkit gem is missing. Add forest_admin_datasource_toolkit as a runtime dependency via spec.add_dependency.
spec.add_dependency 'forest_admin_datasource_toolkit'
+ spec.add_dependency 'activesupport', '>= 6.1'π Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/forest_admin_datasource_pylon/forest_admin_datasource_pylon.gemspec around lines 32-33:
The gemspec omits `forest_admin_datasource_toolkit` from its runtime dependencies, so installing `forest_admin_datasource_pylon` does not pull in the toolkit. When users `require 'forest_admin_datasource_pylon'`, the gem fails with `LoadError` because the toolkit gem is missing. Add `forest_admin_datasource_toolkit` as a runtime dependency via `spec.add_dependency`.
Story 1 of the Pylon datasource β EXT-5. Targets the integration branch
feat/datasource-pylon(EXT-4), notmain.What this adds
A new
forest_admin_datasource_pylongem skeleton with an authenticated, rate-limit-aware HTTP client:for_gemautoloading, typed error hierarchy (Error / ConfigurationError / UnsupportedOperatorError / APIError), configurable logger with aRails.loggerfallback.APIErrorcarries the HTTPstatusand the parsedbodyso the smart actions in Story 8 can surface Pylon's own validation message.Configurationβ validatedapi_key, base URLhttps://api.usepylon.com(Pylon paths are unversioned), configurable timeouts and retry budget.Clientβ Faraday withAuthorization: Bearer, JSON in/out, 429 retry with exponential backoff,GET /mehealth check, plus the envelope-unwrapping and error-mapping helpers the later stories build on.Faraday middleware order β deliberate divergence from the Mambu Payments gem
forest_admin_datasource_mambu_paymentsregistersretrybeforeraise_error, which means 429s are never actually retried:raise_errorsits inside, so it raisesFaraday::TooManyRequestsError, which is not in faraday-retry's defaultexceptionsβ the middleware never observes a response andretry_statusessilently does nothing.This gem inverts the order:
raise_erroroutside the JSON parser (errors carry an already-parsed body) andretryinnermost, where it sees raw statuses. Two specs pin the behaviour β one 429 then 200 issues 2 requests; a persistent 429 issues 3 and raisesAPIErrorwithstatus: 429.Non-idempotent verbs are retried only on 429, where Pylon rejected the request before processing it β a 502 on
POST /issuesmay well have created the issue. Implemented withretry_ifrather thanmethods, because faraday-retry ORs the two conditions andretry_ifcan therefore only widen, never restrict.Worth a separate fix on the Mambu gem.
Monorepo wiring
forest_admin_datasource_pylonadded to thelintmatrix,testmatrix andcoveragefile list inbuild.yml, plus the needed.rubocop.ymlexcludes..releaserc.jsis intentionally untouched, so no incomplete gem can reach RubyGems β thedeployjob only runs on push tomain/beta, so no story PR into the integration branch can publish. The exact 3-line patch is recorded as a comment on EXT-13 (Story 9), which owns "add gem to the release/publish workflow". Consequence: the PylonVERSIONstays at1.36.2and will drift frommainuntil Story 9 realigns it.Test plan
BUNDLE_GEMFILE=Gemfile-test bundle exec rspecβ 29 examples, 0 failures, coverage 96.97% (threshold 90)bundle exec rubocopover the whole repo β 786 files, no offensesClient/Configurationπ€ Generated with Claude Code
Note
Add
forest_admin_datasource_pylongem with configuration and resilient HTTP clientpackages/forest_admin_datasource_pylonwith a gemspec, Zeitwerk autoloading, and a structured error hierarchy (Error,ConfigurationError,APIError).Configurationclass with defaults (base URLhttps://api.usepylon.com, 5s open timeout, 30s timeout, 3 retries) that raisesConfigurationErroron missingapi_key.Clientclass wrapping Faraday with automatic JSON handling, Bearer auth, and retry logic for transient errors (429, 502, 503, 504), including retrying 429 on non-idempotent methods.#meendpoint onClientto validate credentials, returning parsed org data or raisingAPIErrorwith HTTP status and body.π Macroscope summarized 8d94a91. 12 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
ποΈ Filtered Issues
No issues evaluated.