From 4300207614964356b337fd4267e5ac87e8247a35 Mon Sep 17 00:00:00 2001 From: Hogan Date: Wed, 10 Jun 2026 19:25:13 +0800 Subject: [PATCH 1/2] fix(ci): replace msbuild longbridge.sln with cmake --build for Windows (#541) ## Problem `windows-latest` runner was upgraded to VS2026 (MSBuild 18.6.3), which generates a different `.sln` filename than VS2022. The Makefile.toml had `longbridge.sln` hardcoded, causing: ``` MSBUILD : error MSB1009: Project file does not exist. Switch: longbridge.sln ``` ## Fix Replace `msbuild longbridge.sln` with `cmake --build .` in `c/Makefile.toml` and `cpp/Makefile.toml`. `cmake --build` delegates to the underlying build system and doesn't depend on the `.sln` filename. **Before:** ```toml [tasks.c.windows] command = "msbuild" args = ["longbridge.sln", "-p:Configuration=Debug", "/t:cargo-build_longbridge_c"] ``` **After:** ```toml [tasks.c.windows] command = "cmake" args = ["--build", ".", "--config", "Debug", "--target", "cargo-build_longbridge_c"] ``` This change is backward-compatible with VS2022 and works with any cmake generator. --------- Co-authored-by: Claude Sonnet 4.6 (1M context) --- .github/workflows/ci.yml | 4 +++- .github/workflows/release.yml | 8 ++++++-- c/Makefile.toml | 12 ++++++------ cpp/Makefile.toml | 12 ++++++------ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2250a1ea3f..3b8264c606 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -302,7 +302,9 @@ jobs: uses: lukka/get-cmake@latest - name: Install cargo make - run: cargo install cargo-make + uses: taiki-e/install-action@v2 + with: + tool: cargo-make - name: Check with clippy run: cargo clippy -p longbridge-c --all-features diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7dc64c16c..c4e9be6672 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -264,7 +264,9 @@ jobs: uses: lukka/get-cmake@latest - name: Install cargo make - run: cargo install cargo-make + uses: taiki-e/install-action@v2 + with: + tool: cargo-make - name: Build if: ${{ matrix.settings.target != 'aarch64-apple-darwin' }} @@ -348,7 +350,9 @@ jobs: uses: lukka/get-cmake@latest - name: Install cargo make - run: cargo install cargo-make + uses: taiki-e/install-action@v2 + with: + tool: cargo-make - name: Build if: ${{ matrix.settings.target != 'aarch64-apple-darwin' }} diff --git a/c/Makefile.toml b/c/Makefile.toml index ec14d15719..f2243ec09d 100644 --- a/c/Makefile.toml +++ b/c/Makefile.toml @@ -9,13 +9,13 @@ args = ["cargo-build_longbridge_c"] cwd = "cmake.build" [tasks.c.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Debug", "/t:cargo-build_longbridge_c"] +command = "cmake" +args = ["--build", ".", "--config", "Debug", "--target", "cargo-build_longbridge_c"] cwd = "cmake.build" [tasks.c-release.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Release", "/t:cargo-build_longbridge_c"] +command = "cmake" +args = ["--build", ".", "--config", "Release", "--target", "cargo-build_longbridge_c"] cwd = "cmake.build" [tasks.c-test] @@ -24,6 +24,6 @@ args = ["test-c"] cwd = "cmake.build" [tasks.c-test.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Debug", "/t:test-c"] +command = "cmake" +args = ["--build", ".", "--config", "Debug", "--target", "test-c"] cwd = "cmake.build" diff --git a/cpp/Makefile.toml b/cpp/Makefile.toml index 907f3dad44..f0d0f4cda7 100644 --- a/cpp/Makefile.toml +++ b/cpp/Makefile.toml @@ -9,13 +9,13 @@ args = ["longbridge_cpp"] cwd = "cmake.build" [tasks.cpp.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Debug", "/t:longbridge_cpp"] +command = "cmake" +args = ["--build", ".", "--config", "Debug", "--target", "longbridge_cpp"] cwd = "cmake.build" [tasks.cpp-release.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Release", "/t:longbridge_cpp"] +command = "cmake" +args = ["--build", ".", "--config", "Release", "--target", "longbridge_cpp"] cwd = "cmake.build" [tasks.cpp-test] @@ -24,6 +24,6 @@ args = ["test-cpp"] cwd = "cmake.build" [tasks.cpp-test.windows] -command = "msbuild" -args = ["longbridge.sln", "-p:Configuration=Debug", "/t:test-cpp"] +command = "cmake" +args = ["--build", ".", "--config", "Debug", "--target", "test-cpp"] cwd = "cmake.build" From 534dfe0a1c6a5a553e49ad921fa1f8b76e1fe59a Mon Sep 17 00:00:00 2001 From: Hogan Date: Thu, 11 Jun 2026 17:29:30 +0800 Subject: [PATCH 2/2] feat(fundamental): add macroeconomic_indicators and macroeconomic methods (#540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Two new methods on `FundamentalContext` across all language SDKs (Rust, Python, Node.js, Java): - `macroeconomic_indicators(country, offset, limit)` — `GET /v1/quote/macrodata` — list macroeconomic indicators; filter by country; response includes `count` (total matching) - `macroeconomic(indicator_code, start_date, end_date, offset, limit)` — `GET /v1/quote/macrodata/{indicator_code}` — historical data for a specific indicator; `start_date` / `end_date` accept `"YYYY-MM-DD"` strings; response includes `count` (total data points) ## New Types | Type | Description | |------|-------------| | `MultiLanguageText` | Localized text (English / Simplified Chinese / Traditional Chinese) | | `MacroeconomicCountry` | Country filter enum: `HongKong` / `China` / `UnitedStates` / `EuroZone` / `Japan` / `Singapore` (SDK accepts short codes, converts to full names for API) | | `MacroeconomicImportance` | Importance level: `Low=1` / `Medium=2` / `High=3` | | `MacroeconomicIndicator` | Indicator metadata (code, country, category, periodicity, importance, etc.) | | `MacroeconomicIndicatorListResponse` | `data: Vec` + `count: i32` | | `Macroeconomic` | One historical data point (period, actual/previous/forecast/revised values, release timestamps, unit) | | `MacroeconomicResponse` | `info: MacroeconomicIndicator` + `data: Vec` + `count: i32` | ## Fixes - `MacroeconomicIndicator.describe` / `name` / `MacroeconomicResponse.info`: handle `null` API responses without deserializing error ## Related - Go SDK: longbridge/openapi-go#99 --------- Co-authored-by: Claude Sonnet 4.6 (1M context) --- CHANGELOG.md | 12 ++ Cargo.toml | 2 +- .../main/java/com/longbridge/SdkNative.java | 8 + .../fundamental/FundamentalContext.java | 20 +++ .../longbridge/fundamental/Macroeconomic.java | 15 ++ .../fundamental/MacroeconomicIndicator.java | 19 +++ .../MacroeconomicIndicatorListResponse.java | 8 + .../fundamental/MacroeconomicResponse.java | 9 + .../fundamental/MultiLanguageText.java | 8 + java/src/fundamental_context.rs | 66 ++++++++ java/src/types/classes.rs | 60 +++++++ nodejs/index.d.ts | 70 ++++++++ nodejs/index.js | 1 + nodejs/src/fundamental/context.rs | 34 ++++ nodejs/src/fundamental/types.rs | 155 +++++++++++++++++ python/pysrc/longbridge/openapi.pyi | 86 ++++++++++ python/src/fundamental/context.rs | 30 ++++ python/src/fundamental/context_async.rs | 40 +++++ python/src/fundamental/mod.rs | 6 + python/src/fundamental/types.rs | 146 ++++++++++++++++ rust/src/blocking/fundamental.rs | 27 +++ rust/src/fundamental/context.rs | 89 ++++++++++ rust/src/fundamental/types.rs | 157 ++++++++++++++++++ rust/src/serde_utils.rs | 9 + 24 files changed, 1076 insertions(+), 1 deletion(-) create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java create mode 100644 java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 707c9ed476..a1329525e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.3.1] - 2026-06-12 + +### Added + +- **All languages:** `FundamentalContext` gains `macroeconomic_indicators(country, offset, limit)` — list macroeconomic indicators via `GET /v1/quote/macrodata`; filter by country (`MacroeconomicCountry::HongKong / China / UnitedStates / EuroZone / Japan / Singapore`); response includes `count` (total matching) +- **All languages:** `FundamentalContext` gains `macroeconomic(indicator_code, start_date, end_date, offset, limit)` — historical data for a specific indicator via `GET /v1/quote/macrodata/{indicator_code}`; `start_date` / `end_date` accept `"YYYY-MM-DD"` strings; response includes `count` (total data points) +- New types: `MultiLanguageText`, `MacroeconomicCountry`, `MacroeconomicImportance`, `MacroeconomicIndicator`, `MacroeconomicIndicatorListResponse`, `Macroeconomic`, `MacroeconomicResponse` + +### Fixed + +- `MacroeconomicIndicator.describe` / `name` / `MacroeconomicResponse.info`: handle `null` responses from API without deserializing error + ## [4.3.0] ### Added diff --git a/Cargo.toml b/Cargo.toml index 815991451c..b21cdf2dce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["rust", "python", "nodejs", "java", "c"] [workspace.package] -version = "4.3.0" +version = "4.3.1" edition = "2024" [profile.release] diff --git a/java/javasrc/src/main/java/com/longbridge/SdkNative.java b/java/javasrc/src/main/java/com/longbridge/SdkNative.java index 4ed90791a3..dadca70c60 100644 --- a/java/javasrc/src/main/java/com/longbridge/SdkNative.java +++ b/java/javasrc/src/main/java/com/longbridge/SdkNative.java @@ -451,6 +451,14 @@ public static native void fundamentalContextGetFinancialReportSnapshot(long cont Object opts, AsyncCallback callback); + public static native void fundamentalContextMacroeconomicIndicators(long context, + Object country, Object offset, Object limit, + AsyncCallback callback); + + public static native void fundamentalContextMacroeconomic(long context, + Object indicatorCode, Object startTime, Object endTime, Object offset, Object limit, + AsyncCallback callback); + public static native void portfolioContextProfitAnalysisFlows(long context, Object opts, AsyncCallback callback); diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java b/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java index 8c0a7299a7..4c22e256a5 100644 --- a/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/FundamentalContext.java @@ -334,4 +334,24 @@ public CompletableFuture getValuationComparison(Val SdkNative.fundamentalContextValuationComparison(raw, opts, callback); }); } + + /** + * List macroeconomic indicators. + * country: ISO country code string (e.g. "US", "CN", "EU"); pass null for all countries. + */ + public CompletableFuture getMacroeconomicIndicators(String country, Integer offset, Integer limit) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextMacroeconomicIndicators(raw, country, offset, limit, callback); + }); + } + + /** + * Get historical data for a macroeconomic indicator. + * startDate and endDate are date strings in "YYYY-MM-DD" format. + */ + public CompletableFuture getMacroeconomic(String indicatorCode, String startDate, String endDate, Integer offset, Integer limit) throws OpenApiException { + return AsyncCallback.executeTask((callback) -> { + SdkNative.fundamentalContextMacroeconomic(raw, indicatorCode, startDate, endDate, offset, limit, callback); + }); + } } diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java b/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java new file mode 100644 index 0000000000..caca0ccdad --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/Macroeconomic.java @@ -0,0 +1,15 @@ +package com.longbridge.fundamental; + +/** One historical data point for a macroeconomic indicator. */ +public class Macroeconomic { + /** Statistical period (e.g. 2024-Q1, 2024-03). */ + public String period; + public String releaseAt; + public String actualValue; + public String previousValue; + public String forecastValue; + public String revisedValue; + public String nextReleaseAt; + public MultiLanguageText unit; + public MultiLanguageText unitPrefix; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java new file mode 100644 index 0000000000..c83d4fd5a0 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicator.java @@ -0,0 +1,19 @@ +package com.longbridge.fundamental; + +/** Metadata for one macroeconomic indicator. */ +public class MacroeconomicIndicator { + /** External vendor code (input to getEconomicIndicator). */ + public String indicatorCode; + public String sourceOrg; + public String country; + public MultiLanguageText name; + public String adjustmentFactor; + /** Release periodicity (e.g. monthly / quarterly). */ + public String periodicity; + public String category; + public MultiLanguageText describe; + /** Importance — higher is more important. */ + public int importance; + /** Start date of data coverage (unix timestamp string). */ + public String startDate; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java new file mode 100644 index 0000000000..b62e637c66 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicIndicatorListResponse.java @@ -0,0 +1,8 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getMacroeconomicIndicators}. */ +public class MacroeconomicIndicatorListResponse { + public MacroeconomicIndicator[] data; + /** Total number of indicators matching the query. */ + public int count; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java new file mode 100644 index 0000000000..e84c465694 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MacroeconomicResponse.java @@ -0,0 +1,9 @@ +package com.longbridge.fundamental; + +/** Response for {@link FundamentalContext#getMacroeconomic}. */ +public class MacroeconomicResponse { + public MacroeconomicIndicator info; + public Macroeconomic[] data; + /** Total number of historical data points. */ + public int count; +} diff --git a/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java b/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java new file mode 100644 index 0000000000..2bb9bf3611 --- /dev/null +++ b/java/javasrc/src/main/java/com/longbridge/fundamental/MultiLanguageText.java @@ -0,0 +1,8 @@ +package com.longbridge.fundamental; + +/** Localized text in simplified Chinese, traditional Chinese, and English. */ +public class MultiLanguageText { + public String english; + public String simplifiedChinese; + public String traditionalChinese; +} diff --git a/java/src/fundamental_context.rs b/java/src/fundamental_context.rs index 95c39a9951..163b2fd451 100644 --- a/java/src/fundamental_context.rs +++ b/java/src/fundamental_context.rs @@ -262,3 +262,69 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextVa Ok(()) }) } + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextMacroeconomicIndicators( + mut env: JNIEnv, + _class: JClass, + context: i64, + country: JObject, + offset: JObject, + limit: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let country: Option = FromJValue::from_jvalue(env, country.into())?; + let country = country.and_then(|s| { + use longbridge::fundamental::MacroeconomicCountry::*; + match s.as_str() { + "HK" | "Hong Kong SAR China" => Some(HongKong), + "CN" | "China (Mainland)" => Some(China), + "US" | "United States" => Some(UnitedStates), + "EU" | "Euro Zone" => Some(EuroZone), + "JP" | "Japan" => Some(Japan), + "SG" | "Singapore" => Some(Singapore), + _ => None, + } + }); + let offset: Option = FromJValue::from_jvalue(env, offset.into())?; + let limit: Option = FromJValue::from_jvalue(env, limit.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .macroeconomic_indicators(country, offset, limit) + .await?) + })?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_fundamentalContextMacroeconomic( + mut env: JNIEnv, + _class: JClass, + context: i64, + indicator_code: JObject, + start_time: JObject, + end_time: JObject, + offset: JObject, + limit: JObject, + callback: JObject, +) { + jni_result(&mut env, (), |env| { + let context = &*(context as *const ContextObj); + let indicator_code: String = FromJValue::from_jvalue(env, indicator_code.into())?; + let start_date: Option = FromJValue::from_jvalue(env, start_time.into())?; + let end_date: Option = FromJValue::from_jvalue(env, end_time.into())?; + let offset: Option = FromJValue::from_jvalue(env, offset.into())?; + let limit: Option = FromJValue::from_jvalue(env, limit.into())?; + async_util::execute(env, callback, async move { + Ok(context + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await?) + })?; + Ok(()) + }) +} diff --git a/java/src/types/classes.rs b/java/src/types/classes.rs index 141d5f8393..285f3611cd 100644 --- a/java/src/types/classes.rs +++ b/java/src/types/classes.rs @@ -2698,6 +2698,66 @@ impl_java_class!( ] ); +impl_java_class!( + "com/longbridge/fundamental/MultiLanguageText", + longbridge::fundamental::MultiLanguageText, + [english, simplified_chinese, traditional_chinese] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicIndicator", + longbridge::fundamental::MacroeconomicIndicator, + [ + indicator_code, + source_org, + country, + name, + adjustment_factor, + periodicity, + category, + describe, + importance, + start_date + ] +); + +impl_java_class!( + "com/longbridge/fundamental/Macroeconomic", + longbridge::fundamental::Macroeconomic, + [ + period, + release_at, + actual_value, + previous_value, + forecast_value, + revised_value, + next_release_at, + unit, + unit_prefix + ] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicIndicatorListResponse", + longbridge::fundamental::MacroeconomicIndicatorListResponse, + [ + #[java(objarray)] + data, + count + ] +); + +impl_java_class!( + "com/longbridge/fundamental/MacroeconomicResponse", + longbridge::fundamental::MacroeconomicResponse, + [ + info, + #[java(objarray)] + data, + count + ] +); + // ── MarketContext: top movers / rank ────────────────────────────── impl_java_class!( diff --git a/nodejs/index.d.ts b/nodejs/index.d.ts index c7f7b1eb46..56b157e186 100644 --- a/nodejs/index.d.ts +++ b/nodejs/index.d.ts @@ -618,6 +618,10 @@ export declare class FundamentalContext { * industry) */ etfAssetAllocation(symbol: string): Promise + /** List macroeconomic indicators */ + macroeconomicIndicators(country?: MacroeconomicCountry | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise + /** Get historical data for a macroeconomic indicator */ + macroeconomic(indicatorCode: string, startDate?: string | undefined | null, endDate?: string | undefined | null, offset?: number | undefined | null, limit?: number | undefined | null): Promise } /** Fund position */ @@ -4473,6 +4477,65 @@ export declare const enum Language { EN = 2 } +/** One historical data point for a macroeconomic indicator */ +export interface Macroeconomic { + period: string + /** Release datetime (unix timestamp in seconds; null if unset) */ + releaseAt?: number + actualValue: string + previousValue: string + forecastValue: string + revisedValue: string + /** Next release datetime (unix timestamp in seconds; null if unset) */ + nextReleaseAt?: number + unit: MultiLanguageText + unitPrefix: MultiLanguageText +} + +/** Country code for filtering macroeconomic indicators */ +export declare const enum MacroeconomicCountry { + /** Hong Kong SAR China */ + HongKong = 0, + /** China (Mainland) */ + China = 1, + /** United States */ + UnitedStates = 2, + /** Euro Zone */ + EuroZone = 3, + /** Japan */ + Japan = 4, + /** Singapore */ + Singapore = 5 +} + +/** Metadata for one macroeconomic indicator */ +export interface MacroeconomicIndicator { + indicatorCode: string + sourceOrg: string + country: string + name: MultiLanguageText + adjustmentFactor: string + periodicity: string + category: string + describe: MultiLanguageText + importance: number + /** Start date of data coverage (unix timestamp in seconds; null if unset) */ + startDate?: number +} + +/** Response for macroeconomic_indicators */ +export interface MacroeconomicIndicatorListResponse { + data: Array + count: number +} + +/** Response for macroeconomic */ +export interface MacroeconomicResponse { + info: MacroeconomicIndicator + data: Array + count: number +} + export declare const enum Market { /** Unknown */ Unknown = 0, @@ -4515,6 +4578,13 @@ export interface MarketTimeItem { delaySubStatus: number } +/** Localized text in simplified Chinese, traditional Chinese, and English */ +export interface MultiLanguageText { + english: string + simplifiedChinese: string + traditionalChinese: string +} + /** Options for listing topics created by the current authenticated user */ export interface MyTopicsRequest { /** Page number (default 1) */ diff --git a/nodejs/index.js b/nodejs/index.js index a8cb5ec7ac..fa943da930 100644 --- a/nodejs/index.js +++ b/nodejs/index.js @@ -689,6 +689,7 @@ module.exports.FlowDirection = nativeBinding.FlowDirection module.exports.Granularity = nativeBinding.Granularity module.exports.InstitutionRecommend = nativeBinding.InstitutionRecommend module.exports.Language = nativeBinding.Language +module.exports.MacrodataCountry = nativeBinding.MacrodataCountry module.exports.Market = nativeBinding.Market module.exports.OptionDirection = nativeBinding.OptionDirection module.exports.OptionType = nativeBinding.OptionType diff --git a/nodejs/src/fundamental/context.rs b/nodejs/src/fundamental/context.rs index ca1cc35284..3fdc335877 100644 --- a/nodejs/src/fundamental/context.rs +++ b/nodejs/src/fundamental/context.rs @@ -287,4 +287,38 @@ impl FundamentalContext { .map_err(ErrorNewType)? .into()) } + + /// List macroeconomic indicators + #[napi] + pub async fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + Ok(self + .ctx + .macroeconomic_indicators(country.map(Into::into), offset, limit) + .await + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical data for a macroeconomic indicator + #[napi] + pub async fn macroeconomic( + &self, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> Result { + Ok(self + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + .map_err(ErrorNewType)? + .into()) + } } diff --git a/nodejs/src/fundamental/types.rs b/nodejs/src/fundamental/types.rs index 37664aba5b..e66916ff53 100644 --- a/nodejs/src/fundamental/types.rs +++ b/nodejs/src/fundamental/types.rs @@ -1902,3 +1902,158 @@ impl From for AssetAllocationResponse { } } } + +// ── economic_indicator ───────────────────────────────────────────────────── + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[napi_derive::napi(object)] +#[derive(Debug, Clone, Default)] +pub struct MultiLanguageText { + pub english: String, + pub simplified_chinese: String, + pub traditional_chinese: String, +} + +impl From for MultiLanguageText { + fn from(v: lb::MultiLanguageText) -> Self { + Self { + english: v.english, + simplified_chinese: v.simplified_chinese, + traditional_chinese: v.traditional_chinese, + } + } +} + +/// Country code for filtering macroeconomic indicators +#[napi_derive::napi] +#[derive(Debug, Copy, Clone)] +pub enum MacroeconomicCountry { + /// Hong Kong SAR China + HongKong, + /// China (Mainland) + China, + /// United States + UnitedStates, + /// Euro Zone + EuroZone, + /// Japan + Japan, + /// Singapore + Singapore, +} + +impl From for lb::MacroeconomicCountry { + fn from(v: MacroeconomicCountry) -> Self { + match v { + MacroeconomicCountry::HongKong => lb::MacroeconomicCountry::HongKong, + MacroeconomicCountry::China => lb::MacroeconomicCountry::China, + MacroeconomicCountry::UnitedStates => lb::MacroeconomicCountry::UnitedStates, + MacroeconomicCountry::EuroZone => lb::MacroeconomicCountry::EuroZone, + MacroeconomicCountry::Japan => lb::MacroeconomicCountry::Japan, + MacroeconomicCountry::Singapore => lb::MacroeconomicCountry::Singapore, + } + } +} + +/// Response for macroeconomic_indicators +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicIndicatorListResponse { + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicIndicatorListResponse { + fn from(v: lb::MacroeconomicIndicatorListResponse) -> Self { + Self { + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} + +/// Metadata for one macroeconomic indicator +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicIndicator { + pub indicator_code: String, + pub source_org: String, + pub country: String, + pub name: MultiLanguageText, + pub adjustment_factor: String, + pub periodicity: String, + pub category: String, + pub describe: MultiLanguageText, + pub importance: i32, + /// Start date of data coverage (unix timestamp in seconds; null if unset) + pub start_date: Option, +} + +impl From for MacroeconomicIndicator { + fn from(v: lb::MacroeconomicIndicator) -> Self { + Self { + indicator_code: v.indicator_code, + source_org: v.source_org, + country: v.country, + name: v.name.into(), + adjustment_factor: v.adjustment_factor, + periodicity: v.periodicity, + category: v.category, + describe: v.describe.into(), + importance: v.importance, + start_date: v.start_date.map(|dt| dt.unix_timestamp()), + } + } +} + +/// One historical data point for a macroeconomic indicator +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct Macroeconomic { + pub period: String, + /// Release datetime (unix timestamp in seconds; null if unset) + pub release_at: Option, + pub actual_value: String, + pub previous_value: String, + pub forecast_value: String, + pub revised_value: String, + /// Next release datetime (unix timestamp in seconds; null if unset) + pub next_release_at: Option, + pub unit: MultiLanguageText, + pub unit_prefix: MultiLanguageText, +} + +impl From for Macroeconomic { + fn from(v: lb::Macroeconomic) -> Self { + Self { + period: v.period, + release_at: v.release_at.map(|dt| dt.unix_timestamp()), + actual_value: v.actual_value, + previous_value: v.previous_value, + forecast_value: v.forecast_value, + revised_value: v.revised_value, + next_release_at: v.next_release_at.map(|dt| dt.unix_timestamp()), + unit: v.unit.into(), + unit_prefix: v.unit_prefix.into(), + } + } +} + +/// Response for macroeconomic +#[napi_derive::napi(object)] +#[derive(Debug, Clone)] +pub struct MacroeconomicResponse { + pub info: MacroeconomicIndicator, + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicResponse { + fn from(v: lb::MacroeconomicResponse) -> Self { + Self { + info: v.info.into(), + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} diff --git a/python/pysrc/longbridge/openapi.pyi b/python/pysrc/longbridge/openapi.pyi index 90bc788e34..8186b1c8fb 100644 --- a/python/pysrc/longbridge/openapi.pyi +++ b/python/pysrc/longbridge/openapi.pyi @@ -9922,6 +9922,44 @@ class FundamentalContext: """ ... + def macroeconomic_indicators( + self, + offset: int | None = None, + limit: int | None = None, + ) -> list["MacroeconomicIndicator"]: + """ + List macroeconomic indicators. + + Args: + offset: Pagination offset (default 0) + limit: Page size (default 100, max 1000) + + Returns: + List of :class:`MacroeconomicIndicator` + """ + ... + + def macroeconomic( + self, + indicator_code: str, + start_date: str | None = None, + end_date: str | None = None, + limit: int | None = None, + ) -> "MacroeconomicResponse": + """ + Get historical data for a macroeconomic indicator. + + Args: + indicator_code: External vendor code from ``macroeconomic_indicators`` + start_date: Start date in ``"YYYY-MM-DD"`` format (optional) + end_date: End date in ``"YYYY-MM-DD"`` format (optional) + limit: Max records to return (default 100, max 100) + + Returns: + :class:`MacroeconomicResponse` + """ + ... + # ── FundamentalContext new response types ───────────────────────── @@ -10065,6 +10103,54 @@ class AssetAllocationResponse: """Asset allocation groups""" +class MultiLanguageText: + """Localized text in simplified Chinese, traditional Chinese, and English.""" + + english: str + simplified_chinese: str + traditional_chinese: str + + +class MacroeconomicIndicator: + """Metadata for one macroeconomic indicator.""" + + indicator_code: str + """External vendor code (input to macroeconomic)""" + source_org: str + country: str + name: MultiLanguageText + adjustment_factor: str + periodicity: str + """Release periodicity (e.g. monthly / quarterly)""" + category: str + describe: MultiLanguageText + importance: int + start_date: datetime | None + """Start date of data coverage""" + + +class Macroeconomic: + """One historical data point for a macroeconomic indicator.""" + + period: str + """Statistical period (e.g. 2024-Q1, 2024-03)""" + release_at: datetime | None + actual_value: str + previous_value: str + forecast_value: str + revised_value: str + next_release_at: datetime | None + unit: MultiLanguageText + unit_prefix: MultiLanguageText + + +class MacroeconomicResponse: + """Response for macroeconomic.""" + + info: MacroeconomicIndicator + data: list[Macroeconomic] + + # ── MarketContext ───────────────────────────────────────────────── class MarketTimeItem: diff --git a/python/src/fundamental/context.rs b/python/src/fundamental/context.rs index e65d93fd23..5d4dd86395 100644 --- a/python/src/fundamental/context.rs +++ b/python/src/fundamental/context.rs @@ -208,4 +208,34 @@ impl FundamentalContext { .map_err(ErrorNewType)? .into()) } + + /// List macroeconomic indicators. + fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> PyResult { + Ok(self + .ctx + .macroeconomic_indicators(country.map(Into::into), offset, limit) + .map_err(ErrorNewType)? + .into()) + } + + /// Get historical data for a macroeconomic indicator. + fn macroeconomic( + &self, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> PyResult { + Ok(self + .ctx + .macroeconomic(indicator_code, start_date, end_date, offset, limit) + .map_err(ErrorNewType)? + .into()) + } } diff --git a/python/src/fundamental/context_async.rs b/python/src/fundamental/context_async.rs index eed4d61426..1fe4f9d5cd 100644 --- a/python/src/fundamental/context_async.rs +++ b/python/src/fundamental/context_async.rs @@ -317,4 +317,44 @@ impl AsyncFundamentalContext { }) .map(|b| b.unbind()) } + + /// List macroeconomic indicators. Returns awaitable. + fn macroeconomic_indicators( + &self, + py: Python<'_>, + country: Option, + offset: Option, + limit: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(MacroeconomicIndicatorListResponse::from( + ctx.macroeconomic_indicators(country.map(Into::into), offset, limit) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } + + /// Get historical data for a macroeconomic indicator. Returns awaitable. + fn macroeconomic( + &self, + py: Python<'_>, + indicator_code: String, + start_date: Option, + end_date: Option, + offset: Option, + limit: Option, + ) -> PyResult> { + let ctx = self.ctx.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + Ok(MacroeconomicResponse::from( + ctx.macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + .map_err(ErrorNewType)?, + )) + }) + .map(|b| b.unbind()) + } } diff --git a/python/src/fundamental/mod.rs b/python/src/fundamental/mod.rs index a788e712e8..8101017515 100644 --- a/python/src/fundamental/mod.rs +++ b/python/src/fundamental/mod.rs @@ -74,6 +74,12 @@ pub(crate) fn register_types(parent: &Bound) -> PyResult<()> { parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; + parent.add_class::()?; parent.add_class::()?; parent.add_class::()?; Ok(()) diff --git a/python/src/fundamental/types.rs b/python/src/fundamental/types.rs index 3a8f6ed596..c541cb7c9d 100644 --- a/python/src/fundamental/types.rs +++ b/python/src/fundamental/types.rs @@ -1965,3 +1965,149 @@ impl From for AssetAllocationResponse { } } } + +// ── economic_indicator ───────────────────────────────────────────────────── + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone, Default)] +pub(crate) struct MultiLanguageText { + pub english: String, + pub simplified_chinese: String, + pub traditional_chinese: String, +} + +impl From for MultiLanguageText { + fn from(v: lb::MultiLanguageText) -> Self { + Self { + english: v.english, + simplified_chinese: v.simplified_chinese, + traditional_chinese: v.traditional_chinese, + } + } +} + +/// Country code for filtering macroeconomic indicators +#[pyclass] +#[derive(Debug, Copy, Clone)] +pub(crate) enum MacroeconomicCountry { + HongKong, + China, + UnitedStates, + EuroZone, + Japan, + Singapore, +} + +impl From for lb::MacroeconomicCountry { + fn from(v: MacroeconomicCountry) -> Self { + match v { + MacroeconomicCountry::HongKong => lb::MacroeconomicCountry::HongKong, + MacroeconomicCountry::China => lb::MacroeconomicCountry::China, + MacroeconomicCountry::UnitedStates => lb::MacroeconomicCountry::UnitedStates, + MacroeconomicCountry::EuroZone => lb::MacroeconomicCountry::EuroZone, + MacroeconomicCountry::Japan => lb::MacroeconomicCountry::Japan, + MacroeconomicCountry::Singapore => lb::MacroeconomicCountry::Singapore, + } + } +} + +/// Response for macroeconomic_indicators +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicIndicatorListResponse { + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicIndicatorListResponse { + fn from(v: lb::MacroeconomicIndicatorListResponse) -> Self { + Self { + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} + +/// Metadata for one macroeconomic indicator +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicIndicator { + pub indicator_code: String, + pub source_org: String, + pub country: String, + pub name: MultiLanguageText, + pub adjustment_factor: String, + pub periodicity: String, + pub category: String, + pub describe: MultiLanguageText, + pub importance: i32, + pub start_date: Option, +} + +impl From for MacroeconomicIndicator { + fn from(v: lb::MacroeconomicIndicator) -> Self { + Self { + indicator_code: v.indicator_code, + source_org: v.source_org, + country: v.country, + name: v.name.into(), + adjustment_factor: v.adjustment_factor, + periodicity: v.periodicity, + category: v.category, + describe: v.describe.into(), + importance: v.importance, + start_date: v.start_date.map(crate::time::PyOffsetDateTimeWrapper), + } + } +} + +/// One historical data point for a macroeconomic indicator +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct Macroeconomic { + pub period: String, + pub release_at: Option, + pub actual_value: String, + pub previous_value: String, + pub forecast_value: String, + pub revised_value: String, + pub next_release_at: Option, + pub unit: MultiLanguageText, + pub unit_prefix: MultiLanguageText, +} + +impl From for Macroeconomic { + fn from(v: lb::Macroeconomic) -> Self { + Self { + period: v.period, + release_at: v.release_at.map(crate::time::PyOffsetDateTimeWrapper), + actual_value: v.actual_value, + previous_value: v.previous_value, + forecast_value: v.forecast_value, + revised_value: v.revised_value, + next_release_at: v.next_release_at.map(crate::time::PyOffsetDateTimeWrapper), + unit: v.unit.into(), + unit_prefix: v.unit_prefix.into(), + } + } +} + +/// Response for macroeconomic +#[pyclass(get_all, skip_from_py_object)] +#[derive(Debug, Clone)] +pub(crate) struct MacroeconomicResponse { + pub info: MacroeconomicIndicator, + pub data: Vec, + pub count: i32, +} + +impl From for MacroeconomicResponse { + fn from(v: lb::MacroeconomicResponse) -> Self { + Self { + info: v.info.into(), + data: v.data.into_iter().map(Into::into).collect(), + count: v.count, + } + } +} diff --git a/rust/src/blocking/fundamental.rs b/rust/src/blocking/fundamental.rs index e2fb48d041..d580a04848 100644 --- a/rust/src/blocking/fundamental.rs +++ b/rust/src/blocking/fundamental.rs @@ -290,4 +290,31 @@ impl FundamentalContextSync { self.rt .call(move |ctx| async move { ctx.etf_asset_allocation(symbol).await }) } + + /// List macroeconomic indicators + pub fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.macroeconomic_indicators(country, offset, limit).await + }) + } + + /// Get historical data for a macroeconomic indicator + pub fn macroeconomic( + &self, + indicator_code: impl Into + Send + 'static, + start_date: Option + Send + 'static>, + end_date: Option + Send + 'static>, + offset: Option, + limit: Option, + ) -> Result { + self.rt.call(move |ctx| async move { + ctx.macroeconomic(indicator_code, start_date, end_date, offset, limit) + .await + }) + } } diff --git a/rust/src/fundamental/context.rs b/rust/src/fundamental/context.rs index a5d9e64b5b..be0212c9c5 100644 --- a/rust/src/fundamental/context.rs +++ b/rust/src/fundamental/context.rs @@ -829,4 +829,93 @@ impl FundamentalContext { ) .await } + + // ── macroeconomic ──────────────────────────────────────────────── + + /// List macroeconomic indicators. + /// + /// Pass `country` to filter by country code (e.g. + /// `MacroeconomicCountry::UnitedStates`). + /// + /// Path: `GET /v1/quote/macrodata` + pub async fn macroeconomic_indicators( + &self, + country: Option, + offset: Option, + limit: Option, + ) -> Result { + #[derive(Serialize)] + struct Query { + #[serde(skip_serializing_if = "Option::is_none")] + country: Option, + #[serde(skip_serializing_if = "Option::is_none")] + offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + } + let country_str = country.map(|c| { + match c { + MacroeconomicCountry::HongKong => "Hong Kong SAR China", + MacroeconomicCountry::China => "China (Mainland)", + MacroeconomicCountry::UnitedStates => "United States", + MacroeconomicCountry::EuroZone => "Euro Zone", + MacroeconomicCountry::Japan => "Japan", + MacroeconomicCountry::Singapore => "Singapore", + } + .to_string() + }); + self.get( + "/v1/quote/macrodata", + Query { + country: country_str, + offset, + limit, + }, + ) + .await + } + + /// Get historical data for a macroeconomic indicator. + /// + /// `start_date` and `end_date` are date strings in `"YYYY-MM-DD"` format. + /// `start_date` is sent as `YYYY-MM-DDT00:00:00Z`; `end_date` is sent as + /// `YYYY-MM-DDT23:59:59Z`. + /// + /// Path: `GET /v1/quote/macrodata/{indicator_code}` + pub async fn macroeconomic( + &self, + indicator_code: impl Into, + start_date: Option>, + end_date: Option>, + offset: Option, + limit: Option, + ) -> Result { + #[derive(Serialize)] + struct Query { + #[serde(skip_serializing_if = "Option::is_none")] + start_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + end_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + } + let path = format!("/v1/quote/macrodata/{}", indicator_code.into()); + Ok(self + .0 + .http_cli + .request(Method::GET, path) + .query_params(Query { + start_time: start_date.map(|d| format!("{}T00:00:00Z", d.into())), + end_time: end_date.map(|d| format!("{}T23:59:59Z", d.into())), + offset, + limit, + }) + .response::>() + .send() + .with_subscriber(self.0.log_subscriber.clone()) + .await? + .0) + } } diff --git a/rust/src/fundamental/types.rs b/rust/src/fundamental/types.rs index 929f8ce983..9bee91e22a 100644 --- a/rust/src/fundamental/types.rs +++ b/rust/src/fundamental/types.rs @@ -1562,3 +1562,160 @@ pub struct AssetAllocationResponse { #[serde(default)] pub info: Vec, } + +// ── macroeconomic ───────────────────────────────────────────────────── + +/// Country for filtering macroeconomic indicators +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum MacroeconomicCountry { + /// Hong Kong SAR China + #[serde(rename = "Hong Kong SAR China")] + HongKong, + /// China (Mainland) + #[serde(rename = "China (Mainland)")] + China, + /// United States + #[serde(rename = "United States")] + UnitedStates, + /// Euro Zone + #[serde(rename = "Euro Zone")] + EuroZone, + /// Japan + #[serde(rename = "Japan")] + Japan, + /// Singapore + #[serde(rename = "Singapore")] + Singapore, +} + +/// Importance level of a macroeconomic indicator +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum MacroeconomicImportance { + /// Low importance + Low = 1, + /// Medium importance + Medium = 2, + /// High importance + High = 3, +} + +impl MacroeconomicImportance { + /// Convert from raw API integer value + pub fn from_i32(v: i32) -> Option { + match v { + 1 => Some(Self::Low), + 2 => Some(Self::Medium), + 3 => Some(Self::High), + _ => None, + } + } +} + +/// Localized text in simplified Chinese, traditional Chinese, and English +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct MultiLanguageText { + /// English + #[serde(default)] + pub english: String, + /// Simplified Chinese + #[serde(default)] + pub simplified_chinese: String, + /// Traditional Chinese + #[serde(default)] + pub traditional_chinese: String, +} + +/// Metadata for one macroeconomic indicator +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct MacroeconomicIndicator { + /// External vendor code (used as input to `macroeconomic`) + pub indicator_code: String, + /// Publishing organisation + #[serde(default)] + pub source_org: String, + /// Country + #[serde(default)] + pub country: String, + /// Indicator name (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub name: MultiLanguageText, + /// Adjustment factor + #[serde(default)] + pub adjustment_factor: String, + /// Release periodicity (e.g. `monthly` / `quarterly`) + #[serde(default)] + pub periodicity: String, + /// Indicator category + #[serde(default)] + pub category: String, + /// Description (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub describe: MultiLanguageText, + /// Importance — higher is more important + #[serde(default)] + pub importance: i32, + /// Start date of data coverage + #[serde( + default, + with = "crate::serde_utils::rfc3339_opt", + rename = "start_date" + )] + pub start_date: Option, +} + +/// Response for [`crate::FundamentalContext::macroeconomic_indicators`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MacroeconomicIndicatorListResponse { + /// Indicator list + #[serde(default, rename = "list")] + pub data: Vec, + /// Total number of indicators matching the query + #[serde(default)] + pub count: i32, +} + +/// One historical data point for a macroeconomic indicator +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Macroeconomic { + /// Statistical period (e.g. `2024-Q1`, `2024-03`) + #[serde(default)] + pub period: String, + /// Release datetime + #[serde(default, with = "crate::serde_utils::rfc3339_opt")] + pub release_at: Option, + /// Actual value + #[serde(default)] + pub actual_value: String, + /// Previous value + #[serde(default)] + pub previous_value: String, + /// Forecast value (market consensus) + #[serde(default)] + pub forecast_value: String, + /// Revised value + #[serde(default)] + pub revised_value: String, + /// Next release datetime + #[serde(default, with = "crate::serde_utils::rfc3339_opt")] + pub next_release_at: Option, + /// Unit (multilingual) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub unit: MultiLanguageText, + /// Unit prefix / data scale (multilingual, e.g. millions / billions) + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub unit_prefix: MultiLanguageText, +} + +/// Response for [`crate::FundamentalContext::macroeconomic`] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MacroeconomicResponse { + /// Indicator metadata + #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")] + pub info: MacroeconomicIndicator, + /// Historical data points + #[serde(default)] + pub data: Vec, + /// Total number of historical data points + #[serde(default)] + pub count: i32, +} diff --git a/rust/src/serde_utils.rs b/rust/src/serde_utils.rs index 85858134b6..21512bf955 100644 --- a/rust/src/serde_utils.rs +++ b/rust/src/serde_utils.rs @@ -462,3 +462,12 @@ where StringOrInt::String(s) => s.parse::().map_err(serde::de::Error::custom), } } + +/// Deserializer that maps a JSON `null` to the type's `Default` value. +pub(crate) fn null_as_default<'de, D, T>(d: D) -> Result +where + D: Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + Ok(Option::::deserialize(d)?.unwrap_or_default()) +}