diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3356bd054d..75db0c1540 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -245,10 +245,12 @@ jobs: --volume "${PWD}/dist:/artifacts" install: | apt-get update - apt-get install -y --no-install-recommends python3 python3-venv software-properties-common - add-apt-repository ppa:deadsnakes/ppa - apt-get update - apt-get install -y curl python${{ matrix.python-version }}-venv + apt-get install -y --no-install-recommends software-properties-common curl + curl https://pyenv.run | bash + export PATH="$HOME/.pyenv/bin:$PATH" + eval "$(pyenv init --path)" + pyenv install ${{ matrix.python-version }} + pyenv global ${{ matrix.python-version }} run: | ls -lrth /artifacts PYTHON=python${{ matrix.python-version }} diff --git a/.github/workflows/ttt.yml b/.github/workflows/ttt.yml new file mode 100644 index 0000000000..5c2bf6d5f0 --- /dev/null +++ b/.github/workflows/ttt.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: + - ttt2 + pull_request: {} + +jobs: + build-python-sdk-linux-cross: + runs-on: ubuntu-22.04 + strategy: + fail-fast: true + matrix: + python-version: + ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + target: [aarch64] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + manylinux: 2_28 + args: -i python${{ matrix.python-version }} --out dist -m python/Cargo.toml + - uses: uraimo/run-on-arch-action@v3 + name: Install built wheel + with: + arch: none + distro: none + base_image: "--platform=linux/aarch64 ubuntu:22.04" + githubToken: ${{ github.token }} + dockerRunArgs: | + --volume "${PWD}/dist:/artifacts" + install: | + apt-get update + apt-get install -y --no-install-recommends python3 python3-venv software-properties-common gnupg + add-apt-repository ppa:deadsnakes/ppa + apt-get update + apt-get install -y curl python${{ matrix.python-version }}-venv + run: | + ls -lrth /artifacts + PYTHON=python${{ matrix.python-version }} + $PYTHON -m venv venv + venv/bin/pip install -U pip + venv/bin/pip install longport --no-index --find-links /artifacts --force-reinstall + venv/bin/python -c 'import longport' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.target }}-${{ matrix.python-version }} + path: dist diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e15f2721..3cd65bd92e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ 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). +# [3.0.15] 2025-09-29 + +- add `ErrorKind` enum to represent error kinds. + # [3.0.14] 2025-09-05 - fix candlesticks (K-line) might be generated incorrectly in certain situations. diff --git a/Cargo.toml b/Cargo.toml index dff73e70f5..9e275e2d1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["rust", "python", "nodejs", "java", "c", "mcp"] [workspace.package] -version = "3.0.14" +version = "3.0.15" edition = "2024" [profile.release] @@ -52,8 +52,8 @@ leaky-bucket = "1.1.2" pyo3 = "0.26.0" pythonize = "0.26.0" pyo3-build-config = "0.26.0" -napi = { version = "3.2.4", default-features = false } -napi-derive = "3.2.4" +napi = { version = "3.2.5", default-features = false } +napi-derive = "3.2.5" napi-build = "2.2.3" chrono = "0.4.41" poem-mcpserver = "0.2.5" diff --git a/c/cbindgen.toml b/c/cbindgen.toml index f6c17f198d..1ebdf52208 100644 --- a/c/cbindgen.toml +++ b/c/cbindgen.toml @@ -13,6 +13,7 @@ cpp_compat = true "CDecimal" = "lb_decimal_t" "CAsyncCallback" = "lb_async_callback_t" "CAsyncResult" = "lb_async_result_t" +"CErrorKind" = "lb_error_kind_t" "CError" = "lb_error_t" "CConfig" = "lb_config_t" "CSubscription" = "lb_subscription_t" diff --git a/c/csrc/include/longport.h b/c/csrc/include/longport.h index 0789efca83..daa1d701da 100644 --- a/c/csrc/include/longport.h +++ b/c/csrc/include/longport.h @@ -336,6 +336,24 @@ typedef enum lb_deduction_status_t { DeductionStatusDone, } lb_deduction_status_t; +/** + * Error kind + */ +typedef enum lb_error_kind_t { + /** + * HTTP error + */ + ErrorKindHttp, + /** + * OpenAPI error + */ + ErrorKindOpenApi, + /** + * Other error + */ + ErrorKindOther, +} lb_error_kind_t; + /** * Filter warrant expiry date type */ @@ -3850,6 +3868,8 @@ const char *lb_error_message(const struct lb_error_t *error); int64_t lb_error_code(const struct lb_error_t *error); +enum lb_error_kind_t lb_error_kind(const struct lb_error_t *error); + /** * Create a HTTP client */ diff --git a/c/src/error.rs b/c/src/error.rs index 76475684a0..e6867945c3 100644 --- a/c/src/error.rs +++ b/c/src/error.rs @@ -1,10 +1,29 @@ use std::os::raw::c_char; use longport::Error; +use longport_c_macros::CEnum; use crate::types::{CString, ToFFI}; +/// Error kind +#[derive(Debug, Copy, Clone, Eq, PartialEq, CEnum)] +#[c(remote = "longport::SimpleErrorKind")] +#[allow(clippy::enum_variant_names)] +#[repr(C)] +pub enum CErrorKind { + /// HTTP error + #[c(remote = "Http")] + ErrorKindHttp, + /// OpenAPI error + #[c(remote = "OpenApi")] + ErrorKindOpenApi, + /// Other error + #[c(remote = "Other")] + ErrorKindOther, +} + pub struct CError { + kind: CErrorKind, code: i64, message: CString, } @@ -13,6 +32,7 @@ impl From for CError { fn from(err: Error) -> Self { let err = err.into_simple_error(); Self { + kind: err.kind().into(), code: err.code().unwrap_or_default(), message: err.message().to_string().into(), } @@ -43,3 +63,8 @@ pub unsafe extern "C" fn lb_error_message(error: *const CError) -> *const c_char pub unsafe extern "C" fn lb_error_code(error: *const CError) -> i64 { (*error).code } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lb_error_kind(error: *const CError) -> CErrorKind { + (*error).kind +} diff --git a/cpp/include/status.hpp b/cpp/include/status.hpp index 5dae8e4b99..de1a7d0125 100644 --- a/cpp/include/status.hpp +++ b/cpp/include/status.hpp @@ -1,10 +1,19 @@ #pragma once +#include #include typedef struct lb_error_t lb_error_t; namespace longport { + +enum class ErrorKind +{ + Http, + OpenApi, + Other, +}; + class Status { private: @@ -26,11 +35,14 @@ class Status /// Returns `true` if an errors occurs bool is_err() const; - /// Returns the error code - int64_t code() const; + /// Returns the error kind if an error occurs + std::optional kind() const; + + /// Returns the error code if an error occurs + std::optional code() const; - /// Returns the error message - const char* message() const; + /// Returns the error message if an error occurs + std::optional message() const; }; } // namespace longport \ No newline at end of file diff --git a/cpp/src/convert.hpp b/cpp/src/convert.hpp index c5d17f185b..b0757cd8a3 100644 --- a/cpp/src/convert.hpp +++ b/cpp/src/convert.hpp @@ -9,6 +9,7 @@ namespace longport { namespace convert { +using longport::ErrorKind; using longport::quote::AdjustType; using longport::quote::Brokers; using longport::quote::CalcIndex; @@ -118,6 +119,21 @@ convert(Language language) } } +inline ErrorKind +convert(lb_error_kind_t kind) +{ + switch (kind) { + case ErrorKindHttp: + return ErrorKind::Http; + case ErrorKindOpenApi: + return ErrorKind::OpenApi; + case ErrorKindOther: + return ErrorKind::Other; + default: + throw std::invalid_argument("unreachable"); + } +} + inline lb_push_candlestick_mode_t convert(PushCandlestickMode mode) { diff --git a/cpp/src/status.cpp b/cpp/src/status.cpp index 87d8f966e4..bb3cc2a0d7 100644 --- a/cpp/src/status.cpp +++ b/cpp/src/status.cpp @@ -1,4 +1,5 @@ #include "status.hpp" +#include "convert.hpp" #include "longport.h" namespace longport { @@ -50,18 +51,24 @@ Status::is_err() const return err_ != nullptr; } -/// Returns the error code -int64_t +std::optional +Status::kind() const +{ + return err_ ? std::make_optional( + convert::convert(lb_error_kind(err_))) + : std::nullopt; +} + +std::optional Status::code() const { - return lb_error_code(err_); + return err_ ? std::make_optional(lb_error_code(err_)) : std::nullopt; } -/// Returns the error message -const char* +std::optional Status::message() const { - return err_ ? lb_error_message(err_) : "no error"; + return err_ ? std::make_optional(lb_error_message(err_)) : std::nullopt; } } // namespace longport \ No newline at end of file diff --git a/cpp/test/main.cpp b/cpp/test/main.cpp index 4609d679e4..4cacdadc0f 100644 --- a/cpp/test/main.cpp +++ b/cpp/test/main.cpp @@ -18,12 +18,12 @@ main(int argc, char const* argv[]) Status status = Config::from_env(config); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } quote::QuoteContext::create(config, [](auto res) { if (!res) { - std::cout << "failed to create quote context: " << res.status().message() + std::cout << "failed to create quote context: " << *res.status().message() << std::endl; return; } @@ -36,7 +36,7 @@ main(int argc, char const* argv[]) res.context().subscribe( { "700.HK" }, quote::SubFlags::QUOTE(), true, [](auto res) { if (!res) { - std::cout << "failed to subscribe: " << res.status().message() + std::cout << "failed to subscribe: " << *res.status().message() << std::endl; } }); diff --git a/examples/cpp/get_quote/main.cpp b/examples/cpp/get_quote/main.cpp index 7f3e8be636..f5a0e2baf7 100644 --- a/examples/cpp/get_quote/main.cpp +++ b/examples/cpp/get_quote/main.cpp @@ -19,13 +19,13 @@ main(int argc, char const* argv[]) Status status = Config::from_env(config); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } QuoteContext::create(config, [](auto res) { if (!res) { - std::cout << "failed to create quote context: " << res.status().message() + std::cout << "failed to create quote context: " << *res.status().message() << std::endl; return; } @@ -35,7 +35,7 @@ main(int argc, char const* argv[]) }; res.context().quote(symbols, [](auto res) { if (!res) { - std::cout << "failed to get quote: " << res.status().message() + std::cout << "failed to get quote: " << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/history_candlesticks_by_offset/main.cpp b/examples/cpp/history_candlesticks_by_offset/main.cpp index 3882c76fa3..a9efa7f8a1 100644 --- a/examples/cpp/history_candlesticks_by_offset/main.cpp +++ b/examples/cpp/history_candlesticks_by_offset/main.cpp @@ -13,12 +13,12 @@ main() if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } QuoteContext::create(config, [&](auto res) { if (!res) { - std::cout << "failed to create quote context: " << res.status().message() + std::cout << "failed to create quote context: " << *res.status().message() << std::endl; return; } @@ -39,7 +39,7 @@ main() [&](auto res) { if (!res) { std::cout << "failed to request history candlesticks: " - << res.status().message() << std::endl; + << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/http_client/main.cpp b/examples/cpp/http_client/main.cpp index d6bc0d9acc..991ece23f1 100644 --- a/examples/cpp/http_client/main.cpp +++ b/examples/cpp/http_client/main.cpp @@ -18,7 +18,7 @@ main(int argc, char const* argv[]) Status status = http_cli.from_env(); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } @@ -28,7 +28,7 @@ main(int argc, char const* argv[]) std::nullopt, [](auto res) { if (!res) { - std::cout << "failed: " << res.status().message() + std::cout << "failed: " << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/submit_order/main.cpp b/examples/cpp/submit_order/main.cpp index d462d5dc0f..f71c3ae95a 100644 --- a/examples/cpp/submit_order/main.cpp +++ b/examples/cpp/submit_order/main.cpp @@ -19,13 +19,13 @@ main(int argc, char const* argv[]) Status status = Config::from_env(config); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } TradeContext::create(config, [](auto res) { if (!res) { - std::cout << "failed to create trade context: " << res.status().message() + std::cout << "failed to create trade context: " << *res.status().message() << std::endl; return; } @@ -39,7 +39,7 @@ main(int argc, char const* argv[]) }; res.context().submit_order(opts, [](auto res) { if (!res) { - std::cout << "failed to submit order: " << res.status().message() + std::cout << "failed to submit order: " << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/subscribe_candlesticks/main.cpp b/examples/cpp/subscribe_candlesticks/main.cpp index 76e4e4d16f..ce5f583238 100644 --- a/examples/cpp/subscribe_candlesticks/main.cpp +++ b/examples/cpp/subscribe_candlesticks/main.cpp @@ -20,7 +20,7 @@ main(int argc, char const* argv[]) if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } @@ -28,7 +28,7 @@ main(int argc, char const* argv[]) QuoteContext::create(config, [&](auto res) { if (!res) { - std::cout << "failed to create quote context: " << res.status().message() + std::cout << "failed to create quote context: " << *res.status().message() << std::endl; return; } @@ -50,7 +50,7 @@ main(int argc, char const* argv[]) res.context().subscribe_candlesticks( "AAPL.US", Period::Min1, TradeSessions::All, [](auto res) { if (!res) { - std::cout << "failed to subscribe quote: " << res.status().message() + std::cout << "failed to subscribe quote: " << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/subscribe_quote/main.cpp b/examples/cpp/subscribe_quote/main.cpp index 8c1144669a..f43c3c858c 100644 --- a/examples/cpp/subscribe_quote/main.cpp +++ b/examples/cpp/subscribe_quote/main.cpp @@ -19,7 +19,7 @@ main(int argc, char const* argv[]) Status status = Config::from_env(config); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } @@ -27,7 +27,7 @@ main(int argc, char const* argv[]) QuoteContext::create(config, [&](auto res) { if (!res) { - std::cout << "failed to create quote context: " << res.status().message() + std::cout << "failed to create quote context: " << *res.status().message() << std::endl; return; } @@ -49,7 +49,7 @@ main(int argc, char const* argv[]) res.context().subscribe(symbols, SubFlags::QUOTE(), true, [](auto res) { if (!res) { - std::cout << "failed to subscribe quote: " << res.status().message() + std::cout << "failed to subscribe quote: " << *res.status().message() << std::endl; return; } diff --git a/examples/cpp/today_orders/main.cpp b/examples/cpp/today_orders/main.cpp index b7d60eb2cd..8c6a1e8deb 100644 --- a/examples/cpp/today_orders/main.cpp +++ b/examples/cpp/today_orders/main.cpp @@ -19,20 +19,20 @@ main(int argc, char const* argv[]) Status status = Config::from_env(config); if (!status) { std::cout << "failed to load configuration from environment: " - << status.message() << std::endl; + << *status.message() << std::endl; return -1; } TradeContext::create(config, [](auto res) { if (!res) { - std::cout << "failed to create trade context: " << res.status().message() + std::cout << "failed to create trade context: " << *res.status().message() << std::endl; return; } res.context().today_orders(std::nullopt, [](auto res) { if (!res) { - std::cout << "failed to get today orders: " << res.status().message() + std::cout << "failed to get today orders: " << *res.status().message() << std::endl; return; } diff --git a/java/Makefile.toml b/java/Makefile.toml index 060596a00c..8c96a34daf 100644 --- a/java/Makefile.toml +++ b/java/Makefile.toml @@ -28,6 +28,7 @@ args = [ "-d", "classes", + "javasrc/src/main/java/com/longport/ErrorKind.java", "javasrc/src/main/java/com/longport/AsyncCallback.java", "javasrc/src/main/java/com/longport/Config.java", "javasrc/src/main/java/com/longport/ConfigBuilder.java", diff --git a/java/javasrc/src/main/java/com/longport/ErrorKind.java b/java/javasrc/src/main/java/com/longport/ErrorKind.java new file mode 100644 index 0000000000..8a3d96aa92 --- /dev/null +++ b/java/javasrc/src/main/java/com/longport/ErrorKind.java @@ -0,0 +1,19 @@ +package com.longport; + +/** + * Error kind + */ +public enum ErrorKind { + /** + * HTTP error + */ + Http, + /** + * OpenAPI error + */ + OpenApi, + /** + * Other error + */ + Other, +} \ No newline at end of file diff --git a/java/javasrc/src/main/java/com/longport/OpenApiException.java b/java/javasrc/src/main/java/com/longport/OpenApiException.java index 1583c23296..18eae02238 100644 --- a/java/javasrc/src/main/java/com/longport/OpenApiException.java +++ b/java/javasrc/src/main/java/com/longport/OpenApiException.java @@ -1,14 +1,20 @@ package com.longport; public class OpenApiException extends Exception { + private ErrorKind kind; private Long code; private String message; - public OpenApiException(Long code, String message) { + public OpenApiException(ErrorKind kind, Long code, String message) { + this.kind = kind; this.code = code; this.message = message; } + public ErrorKind getKind() { + return kind; + } + public Long getCode() { return code; } @@ -19,6 +25,6 @@ public String getMessage() { @Override public String toString() { - return "OpenApiException [code=" + code + ", message=" + message + "]"; + return "OpenApiException [kind=" + kind + ", code=" + code + ", message=" + message + "]"; } } diff --git a/java/src/error.rs b/java/src/error.rs index 64470b2005..b0f52ec995 100644 --- a/java/src/error.rs +++ b/java/src/error.rs @@ -6,7 +6,10 @@ use jni::{ objects::{JObject, JThrowable, JValue}, }; -use crate::init::{LONG_CLASS, OPENAPI_EXCEPTION_CLASS}; +use crate::{ + init::{LONG_CLASS, OPENAPI_EXCEPTION_CLASS}, + types::IntoJValue, +}; #[derive(Debug, thiserror::Error)] pub(crate) enum JniError { @@ -44,6 +47,7 @@ impl JniError { let exception_cls = OPENAPI_EXCEPTION_CLASS.get().unwrap(); let err = err.into_simple_error(); + let kind = err.kind().into_jvalue(env)?; let code = match err.code() { Some(code) => { env.new_object(LONG_CLASS.get().unwrap(), "(J)V", &[JValue::from(code)])? @@ -54,8 +58,8 @@ impl JniError { env.new_object( exception_cls, - "(Ljava/lang/Long;Ljava/lang/String;)V", - &[JValue::from(&code), JValue::from(&message)], + "(Lcom/longport/ErrorKind;Ljava/lang/Long;Ljava/lang/String;)V", + &[kind.borrow(), JValue::from(&code), JValue::from(&message)], ) } diff --git a/java/src/init.rs b/java/src/init.rs index 644cdd987e..c8945282b1 100644 --- a/java/src/init.rs +++ b/java/src/init.rs @@ -78,6 +78,7 @@ pub extern "system" fn Java_com_longport_SdkNative_init<'a>( // enum types init_class_by_classloader!( env, + longport::SimpleErrorKind, longport::Language, longport::PushCandlestickMode, longport::Market, diff --git a/java/src/types/enum_types.rs b/java/src/types/enum_types.rs index 98eb5241ca..70e59db9e5 100644 --- a/java/src/types/enum_types.rs +++ b/java/src/types/enum_types.rs @@ -12,6 +12,12 @@ use crate::{ types::{IntoJValue, JSignature}, }; +impl_java_enum!( + "com/longport/ErrorKind", + longport::SimpleErrorKind, + [Http, OpenApi, Other] +); + impl_java_enum!( "com/longport/Language", longport::Language, diff --git a/python/Makefile.toml b/python/Makefile.toml index e21224df2d..4ca2dbe2c1 100644 --- a/python/Makefile.toml +++ b/python/Makefile.toml @@ -12,7 +12,7 @@ args = ["install", "maturin>=1.0,<2.0"] command = "pip" args = [ "install", - "target/wheels/longport-3.0.9-cp311-cp311-win_amd64.whl", + "target/wheels/longport-3.0.15-cp311-cp311-win_amd64.whl", "-I", ] dependencies = ["python"] diff --git a/python/pysrc/longport/__init__.py b/python/pysrc/longport/__init__.py index 888594ccd7..6169e6a7bb 100644 --- a/python/pysrc/longport/__init__.py +++ b/python/pysrc/longport/__init__.py @@ -1,19 +1,21 @@ import sys from .longport import openapi +from typing import Optional sys.modules['longport.openapi'] = openapi class OpenApiException(Exception): - def __init__(self, code: int, trace_id: str, message: str): + def __init__(self, kind, code, trace_id, message): + self.kind = kind self.code = code self.trace_id = trace_id self.message = message def __str__(self): if self.code != None: - return "OpenApiException: (code=%d, trace_id=%s) %s" % (self.code, self.trace_id, self.message) + return "OpenApiException: (kind=%s, code=%d, trace_id=%s) %s" % (self.kind, self.code, self.trace_id, self.message) else: return "OpenApiException: %s" % self.message diff --git a/python/pysrc/longport/openapi.pyi b/python/pysrc/longport/openapi.pyi index 97d11454b1..26d898e585 100644 --- a/python/pysrc/longport/openapi.pyi +++ b/python/pysrc/longport/openapi.pyi @@ -3,12 +3,38 @@ from decimal import Decimal from typing import Any, Callable, List, Optional, Type +class ErrorKind: + """ + Error kind + """ + + class Http(ErrorKind): + """ + HTTP error + """ + + class OpenApi(ErrorKind): + """ + OpenApi error + """ + + class Other(ErrorKind): + """ + Other error + """ + + class OpenApiException(Exception): """ OpenAPI exception """ - code: Optional[int] + kind: ErrorKind + """ + Error kind + """ + + code: int """ Error code """ diff --git a/python/src/error.rs b/python/src/error.rs index b27c47ff0d..686eae87da 100644 --- a/python/src/error.rs +++ b/python/src/error.rs @@ -1,4 +1,5 @@ -use pyo3::PyErr; +use longport_python_macros::PyEnum; +use pyo3::{PyErr, pyclass}; pyo3::import_exception!(longport.openapi, OpenApiException); @@ -9,9 +10,22 @@ impl std::convert::From for PyErr { fn from(err: ErrorNewType) -> PyErr { let err = err.0.into_simple_error(); OpenApiException::new_err(( + ErrorKind::from(err.kind()), err.code(), err.trace_id().map(ToString::to_string), err.message().to_string(), )) } } + +#[pyclass(eq, eq_int)] +#[derive(Debug, PyEnum, Copy, Clone, Hash, Eq, PartialEq)] +#[py(remote = "longport::SimpleErrorKind")] +pub(crate) enum ErrorKind { + /// HTTP error + Http, + /// OpenAPI error + OpenApi, + /// Other error + Other, +} diff --git a/python/src/lib.rs b/python/src/lib.rs index a0d380bc4b..4675d80d3b 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -18,6 +18,7 @@ fn longport(py: Python<'_>, m: Bound) -> PyResult<()> { openapi.add_class::()?; openapi.add_class::()?; openapi.add_class::()?; + openapi.add_class::()?; quote::register_types(&openapi)?; trade::register_types(&openapi)?; diff --git a/rust/src/error.rs b/rust/src/error.rs index f788320127..80359b8f07 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -96,15 +96,24 @@ impl Error { code, message, trace_id, - }) => SimpleError::Response { + }) => SimpleError::OpenApi { code: code as i64, message, trace_id, }, + Error::HttpClient(HttpClientError::Http(err)) => { + if let Some(status) = err.0.status() { + SimpleError::Http { + status_code: status.as_u16(), + } + } else { + SimpleError::Other(err.to_string()) + } + } Error::WsClient(WsClientError::ResponseError { detail: Some(detail), .. - }) => SimpleError::Response { + }) => SimpleError::OpenApi { code: detail.code as i64, message: detail.msg, trace_id: String::new(), @@ -130,9 +139,15 @@ pub type Result = ::std::result::Result; /// Simple error type #[derive(Debug, thiserror::Error)] pub enum SimpleError { - /// Response error - #[error("response error: code={code} message={message}")] - Response { + /// Http error + #[error("http error: status_code={status_code}")] + Http { + /// HTTP status code + status_code: u16, + }, + /// OpenAPI error + #[error("openapi error: code={code} message={message}")] + OpenApi { /// Error code code: i64, /// Error message @@ -152,11 +167,32 @@ impl From for SimpleError { } } +/// Simple error kind +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SimpleErrorKind { + /// HTTP error + Http, + /// OpenAPI error + OpenApi, + /// Other error + Other, +} + impl SimpleError { + /// Returns the kind of this error + pub fn kind(&self) -> SimpleErrorKind { + match self { + SimpleError::Http { .. } => SimpleErrorKind::Http, + SimpleError::OpenApi { .. } => SimpleErrorKind::OpenApi, + SimpleError::Other(_) => SimpleErrorKind::Other, + } + } + /// Returns the error code pub fn code(&self) -> Option { match self { - SimpleError::Response { code, .. } => Some(*code), + SimpleError::Http { status_code } => Some(*status_code as i64), + SimpleError::OpenApi { code, .. } => Some(*code), SimpleError::Other(_) => None, } } @@ -164,7 +200,8 @@ impl SimpleError { /// Returns the trace id pub fn trace_id(&self) -> Option<&str> { match self { - SimpleError::Response { trace_id, .. } => Some(trace_id), + SimpleError::Http { .. } => None, + SimpleError::OpenApi { trace_id, .. } => Some(trace_id), SimpleError::Other(_) => None, } } @@ -172,7 +209,8 @@ impl SimpleError { /// Returns the error message pub fn message(&self) -> &str { match self { - SimpleError::Response { message, .. } => message.as_str(), + SimpleError::Http { .. } => "bad status code", + SimpleError::OpenApi { message, .. } => message.as_str(), SimpleError::Other(message) => message.as_str(), } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index cc1425601e..6248774538 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -20,7 +20,7 @@ pub mod quote; pub mod trade; pub use config::{Config, Language, PushCandlestickMode}; -pub use error::{Error, Result, SimpleError}; +pub use error::{Error, Result, SimpleError, SimpleErrorKind}; pub use longport_httpcli as httpclient; pub use longport_wscli as wsclient; pub use quote::QuoteContext;