pub struct LseDataApiClient { /* private fields */ }Expand description
Authenticated client for the London Strategic Edge data API.
Wraps the internal transport shared with the vault, pointed at this host, and adds its
endpoints: bond yields in bond_yield.
§Request rationing is per client, not per call
Every request this client issues first passes a gate that bounds requests in flight to
with_concurrency and spaces their starts by
with_pace. That gate is shared by clones, so driving N concurrent
fetches from one client (Arc it, or clone it) keeps the aggregate inside those two bounds
rather than multiplying them by N. Two clients built separately by new share nothing and
ration independently — one client per API key is the usable unit.
§⚠️ Licensing
Data retrieved through this client is not redistributable. See the module documentation and https://londonstrategicedge.com/terms.
Implementations§
Source§impl LseDataApiClient
impl LseDataApiClient
Sourcepub async fn fetch_bond_yield_stats(
&self,
) -> Result<LseBondYieldStats, LseError>
pub async fn fetch_bond_yield_stats( &self, ) -> Result<LseBondYieldStats, LseError>
Fetch the provider’s index of published bond-yield coverage.
Fetch this once and reuse the handle across queries — see LseBondYieldStats. It is
the required first argument to fetch_bond_yields, which is what
makes a query checkable against an endpoint that validates nothing itself.
§Errors
See LseError.
Sourcepub async fn fetch_bond_yields(
&self,
stats: &LseBondYieldStats,
query: &LseBondYieldQuery,
) -> Result<Vec<LseBondYieldRow>, LseError>
pub async fn fetch_bond_yields( &self, stats: &LseBondYieldStats, query: &LseBondYieldQuery, ) -> Result<Vec<LseBondYieldRow>, LseError>
Fetch daily bond-yield observations for query.
stats is required, not optional, and query is validated against it before anything is
sent. That is a deliberate API choice: the endpoint answers 200 with zero rows for an
unknown country, an unknown tenor and an empty window alike, so a fetch that could skip
validation would be a fetch that could silently return nothing. See the
module documentation for the measurements.
Rows arrive ascending by date, oldest first, and the country code sent is the normalised
one — a caller passing GB reaches the provider’s UK.
§The whole answer arrives in one response
This endpoint is not paged: its envelope carries no cursor, and a request for the full
published history of every United States tenor returned all 76,406 rows in a single 4-second
response, matching /bond-yields/stats exactly. So this returns a Vec rather than a
stream, and no pagination is invented for a surface that has none.
The envelope’s own count is checked against the rows delivered and a mismatch is an error
rather than a short result — that is the signal a silent page cap would produce if the
provider ever introduced one.
§⚠️ An unnarrowed query is a large request
Omitting maturity fetches every tenor, which for a
deep country over its full history is tens of thousands of rows against the client’s 30s
total request timeout. Narrow the tenor or the window for anything latency-sensitive.
§Errors
Anything validate raises, plus LseError’s transport and
decode variants. A response whose count disagrees with the rows delivered is reported as
LseError::Api carrying the 200 the provider actually sent.
Source§impl LseDataApiClient
impl LseDataApiClient
Sourcepub async fn fetch_economic_calendar_stats(
&self,
) -> Result<LseCalendarStats, LseError>
pub async fn fetch_economic_calendar_stats( &self, ) -> Result<LseCalendarStats, LseError>
Fetch the provider’s index of published economic-calendar coverage.
Fetch this once and reuse the handle across queries — see LseCalendarStats. It is
the required first argument to
fetch_economic_calendar, which is what makes a query
checkable against an endpoint that validates nothing itself.
It is also how a caller detects the frozen feed reviving:
latest has not moved past 2026-03-24 across measurements two
months apart.
§Errors
See LseError.
Sourcepub async fn fetch_economic_calendar(
&self,
stats: &LseCalendarStats,
query: &LseCalendarQuery,
) -> Result<Vec<LseCalendarEvent>, LseError>
pub async fn fetch_economic_calendar( &self, stats: &LseCalendarStats, query: &LseCalendarQuery, ) -> Result<Vec<LseCalendarEvent>, LseError>
Fetch economic-calendar events for query.
stats is required, not optional, and query is validated against it before anything
is sent. That is a deliberate API choice: the endpoint answers 200 with zero rows for an
unknown country, an unknown impact rating, a reversed range and an empty window alike, so
a fetch that could skip validation would be a fetch that could silently return nothing. See
the module documentation for the measurements.
The country codes sent are the normalised ones — a caller passing GB reaches the
provider’s UK — joined into a single comma-separated parameter.
§🔴 Nothing is published after 2026-03-24
The feed is frozen. A window after that date is rejected by validation; a window spanning it returns only what exists up to it. This endpoint serves a historical archive, not a forward-looking calendar. See the module documentation.
§🔴 An empty result is not necessarily an error
The provider publishes no per-country coverage, and coverage is very uneven — 88 of 108
countries hold fewer than 100 events, and UK holds 56, all in early 2026. A query for a
published country inside the global range can legitimately return an empty Vec, and
that is not reported as an error, because the library has no way to distinguish it from
a quiet period. Only the three checks validation can actually make are made.
§The whole answer arrives in one response
This endpoint is not paged: its envelope carries no cursor, the provider’s OpenAPI
document declares no limit, offset, cursor or page parameter, and a request for the
entire corpus with no country filter returned all 124,896 events in a single 29 MB
response, matching total_events exactly. So this returns a Vec rather than a stream, and
no pagination is invented for a surface that has none.
The envelope’s own count is checked against the events delivered and a mismatch is an
error rather than a short result — that is the signal a silent page cap would produce if the
provider ever introduced one.
§⚠️ An unnarrowed query is a large request
That whole-corpus fetch took 6.3 seconds, against this client’s 30-second total request timeout. It is the measured worst case rather than a typical one, but the margin is not enormous: narrow the countries or the window for anything latency-sensitive.
§Errors
Anything validate raises, plus LseError’s transport and
decode variants. A response whose count disagrees with the events delivered is reported as
LseError::Api carrying the 200 the provider actually sent.
Source§impl LseDataApiClient
impl LseDataApiClient
Sourcepub fn new(api_key: &str) -> Result<Self, LseError>
pub fn new(api_key: &str) -> Result<Self, LseError>
Create a client with an explicit API key.
§Redirects are not followed
The key rides every request as a client-wide x-api-key default header, and reqwest’s
cross-host header stripping covers Authorization, Cookie, Cookie2,
Proxy-Authorization and WWW-Authenticate — not a custom header. Under reqwest’s
default Policy::limited(10) a single server-issued
302 would therefore re-issue the request, live key attached, against whatever host this one
named.
Policy::none makes the “this client only ever requests
URLs it builds itself” invariant structural rather than a property of the provider’s current
behaviour — a followed redirect is a server-supplied URL. An unexpected 3xx surfaces as a
typed error instead of being followed, and it applies wholesale: a same-origin 301/308
(trailing-slash normalisation by a proxy or CDN) is surfaced too, so a base URL set via
with_base_url must serve responses directly.
§Errors
Returns LseError::InvalidCredential if the key cannot be encoded as an HTTP header
value (e.g. non-ASCII bytes), or LseError::Http if the HTTP client cannot be built.
Sourcepub fn from_env() -> Result<Self, LseError>
pub fn from_env() -> Result<Self, LseError>
Create a client from the LSE_API_KEY environment variable.
The same variable the vault client reads — one key authenticates both hosts.
§Errors
Returns LseError::EnvVar if the variable is unset or does not hold valid UTF-8, plus
anything new returns. Neither message ever contains the variable’s value.
Sourcepub fn with_base_url(self, base_url: impl Into<String>) -> Self
pub fn with_base_url(self, base_url: impl Into<String>) -> Self
Override the data API base URL, for tests against a mock server or a proxy.
Infallible by design: this client only ever requests URLs it builds itself from this base —
the endpoints here answer with a self-contained {count, data} envelope carrying no cursor,
so there is never a server-supplied URL to follow and no trusted origin to derive.
Sourcepub fn with_client(self, client: Client) -> Self
pub fn with_client(self, client: Client) -> Self
Inject a pre-built reqwest::Client.
§⚠️ The injected client must carry the x-api-key header itself
This replaces the authenticated client built by new, auth header included.
It is intended for transport configuration (proxy, TLS, a shared connection pool) where the
caller supplies credentials; a client without the header will see every request 401.
§⚠️ It must also carry its own timeouts
new configures a 30s total timeout and a 30s read timeout; reqwest’s
default is neither. Replacing the client drops both, and a timeout-less client
interacts badly with the request gate:
a permit is held for the whole request, so with the default concurrency of 2, two stalled
connections exhaust the gate permanently — for this client and every clone of it, with no
error, no log, and nothing to observe but a run that stops making progress. Set
timeout and
read_timeout on any client passed here.
§⚠️ And its own redirect policy
new sets Policy::none so the API key
cannot ride a server-issued redirect to another host; reqwest’s default is
Policy::limited(10), which strips only the standard auth headers and would forward a
custom x-api-key intact. A client supplied here that carries the key must set
Policy::none itself, or a single 302 leaks the credential with no error and no log.
Sourcepub fn with_pace(self, pace: Duration) -> Self
pub fn with_pace(self, pace: Duration) -> Self
Override the minimum delay between the starts of two requests.
Applies to every request this client issues, and is enforced against the
gate this client shares with its
clones — so N concurrent fetches still start one request per pace between them.
Pass Duration::ZERO to disable pacing entirely, at which point staying within the
provider’s allowance becomes the caller’s responsibility. The default is derived from the
allowance the vault documents; this host publishes no rate of its own, so the vault’s
figure is used rather than an unrationed default.
Sourcepub fn with_concurrency(self, concurrency: usize) -> Self
pub fn with_concurrency(self, concurrency: usize) -> Self
Override how many requests this client may have in flight at once.
0 is clamped to 1, since a client that can never issue a request is never what was
meant.
§⚠️ Call this before cloning
This installs a fresh gate. Clones taken before the call keep the old one and ration separately from this client, which defeats the point of sharing; clones taken after share the new gate as usual.
Trait Implementations§
Source§impl Clone for LseDataApiClient
impl Clone for LseDataApiClient
Auto Trait Implementations§
impl !RefUnwindSafe for LseDataApiClient
impl !UnwindSafe for LseDataApiClient
impl Freeze for LseDataApiClient
impl Send for LseDataApiClient
impl Sync for LseDataApiClient
impl Unpin for LseDataApiClient
impl UnsafeUnpin for LseDataApiClient
Blanket Implementations§
Source§impl<T> AttachedTransformer<()> for T
impl<T> AttachedTransformer<()> for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> JsonSchemaMaybe for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.