Class: SiteSearch::SwiftypeAdapter
- Inherits:
-
Object
- Object
- SiteSearch::SwiftypeAdapter
- Includes:
- Service
- Defined in:
- app/services/site_search/swiftype_adapter.rb
Overview
Queries Swiftype (Elastic Site Search) for everything that isn't a publication.
This used to run in the browser — search_controller.js POSTed to
api.swiftype.com directly with the engine key inlined in the bundle. Moving it
behind our own endpoint means the engine is swappable without touching the
client (see doc/tasks/202512141800_SWIFTYPE_REPLACEMENT.md) and the response
becomes edge-cacheable.
Search must degrade, never 500: any transport or parse failure returns an empty
page so the publications half of a blended query still reaches the user.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- ENDPOINT =
Swiftype's public search endpoint.
'https://api.swiftype.com/api/v1/public/engines/search'- PER_PAGE =
Results requested per page.
10- OPEN_TIMEOUT =
Bounded, because this sits in a user's request path — but not tight. This
covers TCP and TLS, and the handshake to Swiftype measured up to 0.82s
from a developer laptop; a 2s ceiling tripped on a real search and silently
served an empty Product tab (development.log, 2026-08-12). 5- TIMEOUT =
Seconds to wait for the whole call. A slow engine should cost a blended
search its Swiftype half, not the whole response. 8- DOMAIN_IDENTIFIERS =
Swiftype scopes en-CA content by a
domain-identifierfacet. { 'en-CA' => 2 }.freeze
- DEFAULT_DOMAIN_IDENTIFIER =
The US domain facet, used for every locale but en-CA.
1
Class Method Summary collapse
-
.engine_key ⇒ String?
Swiftype's engine key, from credentials (
swiftype_engine_key). -
.proxy_key ⇒ String?
Shared key the worker requires (
x-proxy-key). -
.proxy_url ⇒ String?
Egress proxy (Cloudflare Worker), production-scoped in credentials.
Instance Method Summary collapse
-
#call ⇒ Result
One page of engine results, empty when the engine is down.
Methods included from Service
#attributes_used, #build_result, call, #logger
Methods included from Service::Initializer
Class Method Details
.engine_key ⇒ String?
Swiftype's engine key, from credentials (swiftype_engine_key).
Swiftype calls this the public key — it authorizes search-only access to
one engine — and it shipped inside the JS bundle for years. It lives in
credentials anyway: the secrets gate reads it as an API key because that is
exactly what it is, and rotating it should not need a code change.
Resolved per call rather than at class load so a rotation takes effect on
the next request instead of the next boot.
45 46 47 |
# File 'app/services/site_search/swiftype_adapter.rb', line 45 def self.engine_key Heatwave::Configuration.fetch(:swiftype_engine_key) end |
.proxy_key ⇒ String?
Returns shared key the worker requires (x-proxy-key).
63 64 65 |
# File 'app/services/site_search/swiftype_adapter.rb', line 63 def self.proxy_key Heatwave::Configuration.fetch(:swiftype_proxy_key) end |
.proxy_url ⇒ String?
Egress proxy (Cloudflare Worker), production-scoped in credentials. Swiftype
expects distributed browser traffic; calling it straight from the app server
concentrated every visitor's search onto one IP, and their network banned it
within a minute of a burst (2026-08-13, connect timeouts on every SYN).
Routing through the worker gives the call Cloudflare egress instead. Blank
outside production, so dev talks to Swiftype directly and tests stub the
real endpoint.
58 59 60 |
# File 'app/services/site_search/swiftype_adapter.rb', line 58 def self.proxy_url Heatwave::Configuration.fetch(:swiftype_proxy_url) end |
Instance Method Details
#call ⇒ Result
Returns one page of engine results, empty when the engine is down.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'app/services/site_search/swiftype_adapter.rb', line 83 def call return empty if query.blank? # A missing key means every search would come back empty and look like a # catalogue with nothing in it; say the engine is down instead. return empty(degraded: true) if engine_key.blank? response = post # Anything that isn't a 2xx — a 429, a 5xx, or a transport failure — means # the engine did not answer. Only a successful reply can say "no matches". unless response&.success? report_rejection(response) if response return empty(degraded: true) end parse(response.body) end |