Skip to main content

LseDataApiClient

Struct LseDataApiClient 

Source
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

Source

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.

Source

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

Source

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.

Source

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LseDataApiClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AttachedTransformer<()> for T

Source§

fn attached(&mut self, _: ())

Take what the connection handed the stream on attaching.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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 more
Source§

impl<T> JsonSchemaMaybe for T

Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows 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
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows 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
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .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
where Self: BorrowMut<B>, B: ?Sized,

Calls .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
where Self: AsRef<R>, R: ?Sized,

Calls .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
where Self: AsMut<R>, R: ?Sized,

Calls .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
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more