Skip to main content

oanda_client/models/
variants.rs

1//! Tagged provider unions for orders and transactions.
2//!
3//! Response unions preserve unknown tagged objects as evidence. Request unions
4//! reject unknown variants because their trading semantics have not been defined.
5use super::{
6    ClientConfigureRejectTransaction, ClientConfigureTransaction, CloseTransaction,
7    CreateTransaction, DailyFinancingTransaction, DelayedTradeClosureTransaction,
8    DividendAdjustmentTransaction, FixedPriceOrder, FixedPriceOrderTransaction,
9    GuaranteedStopLossOrder, GuaranteedStopLossOrderRejectTransaction,
10    GuaranteedStopLossOrderRequest, GuaranteedStopLossOrderTransaction, LimitOrder,
11    LimitOrderRejectTransaction, LimitOrderRequest, LimitOrderTransaction,
12    MarginCallEnterTransaction, MarginCallExitTransaction, MarginCallExtendTransaction,
13    MarketIfTouchedOrder, MarketIfTouchedOrderRejectTransaction, MarketIfTouchedOrderRequest,
14    MarketIfTouchedOrderTransaction, MarketOrder, MarketOrderRejectTransaction, MarketOrderRequest,
15    MarketOrderTransaction, OrderCancelRejectTransaction, OrderCancelTransaction,
16    OrderClientExtensionsModifyRejectTransaction, OrderClientExtensionsModifyTransaction,
17    OrderFillTransaction, ReopenTransaction, ResetResettablePLTransaction, StopLossOrder,
18    StopLossOrderRejectTransaction, StopLossOrderRequest, StopLossOrderTransaction, StopOrder,
19    StopOrderRejectTransaction, StopOrderRequest, StopOrderTransaction, TakeProfitOrder,
20    TakeProfitOrderRejectTransaction, TakeProfitOrderRequest, TakeProfitOrderTransaction,
21    TradeClientExtensionsModifyRejectTransaction, TradeClientExtensionsModifyTransaction,
22    TrailingStopLossOrder, TrailingStopLossOrderRejectTransaction, TrailingStopLossOrderRequest,
23    TrailingStopLossOrderTransaction, TransferFundsRejectTransaction, TransferFundsTransaction,
24};
25use serde::{Deserialize, Serialize};
26
27/// Documented Order variants identified by OANDA’s `type` field.
28/// Unknown provider objects retain their discriminator and raw fields for inspection.
29#[derive(Debug, Clone, PartialEq)]
30#[non_exhaustive]
31pub enum Order {
32    /// `MARKET` provider variant.
33    MarketOrder(MarketOrder),
34    /// `LIMIT` provider variant.
35    LimitOrder(LimitOrder),
36    /// `STOP` provider variant.
37    StopOrder(StopOrder),
38    /// `MARKET_IF_TOUCHED` provider variant.
39    MarketIfTouchedOrder(MarketIfTouchedOrder),
40    /// `TAKE_PROFIT` provider variant.
41    TakeProfitOrder(TakeProfitOrder),
42    /// `STOP_LOSS` provider variant.
43    StopLossOrder(StopLossOrder),
44    /// `GUARANTEED_STOP_LOSS` provider variant.
45    GuaranteedStopLossOrder(GuaranteedStopLossOrder),
46    /// `TRAILING_STOP_LOSS` provider variant.
47    TrailingStopLossOrder(TrailingStopLossOrder),
48    /// `FIXED_PRICE` provider variant.
49    FixedPriceOrder(FixedPriceOrder),
50    /// Future provider variant with its bounded raw value.
51    Unknown {
52        /// Provider discriminator.
53        kind: String,
54        /// Bounded provider object.
55        raw: serde_json::Value,
56    },
57}
58
59impl<'de> Deserialize<'de> for Order {
60    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
61        let value = serde_json::Value::deserialize(deserializer)?;
62        let kind = discriminator(&value).map_err(serde::de::Error::custom)?;
63        match kind {
64            "MARKET" => serde_json::from_value(value)
65                .map(Self::MarketOrder)
66                .map_err(serde::de::Error::custom),
67            "LIMIT" => serde_json::from_value(value)
68                .map(Self::LimitOrder)
69                .map_err(serde::de::Error::custom),
70            "STOP" => serde_json::from_value(value)
71                .map(Self::StopOrder)
72                .map_err(serde::de::Error::custom),
73            "MARKET_IF_TOUCHED" => serde_json::from_value(value)
74                .map(Self::MarketIfTouchedOrder)
75                .map_err(serde::de::Error::custom),
76            "TAKE_PROFIT" => serde_json::from_value(value)
77                .map(Self::TakeProfitOrder)
78                .map_err(serde::de::Error::custom),
79            "STOP_LOSS" => serde_json::from_value(value)
80                .map(Self::StopLossOrder)
81                .map_err(serde::de::Error::custom),
82            "GUARANTEED_STOP_LOSS" => serde_json::from_value(value)
83                .map(Self::GuaranteedStopLossOrder)
84                .map_err(serde::de::Error::custom),
85            "TRAILING_STOP_LOSS" => serde_json::from_value(value)
86                .map(Self::TrailingStopLossOrder)
87                .map_err(serde::de::Error::custom),
88            "FIXED_PRICE" => serde_json::from_value(value)
89                .map(Self::FixedPriceOrder)
90                .map_err(serde::de::Error::custom),
91            other => Ok(Self::Unknown {
92                kind: other.to_owned(),
93                raw: value,
94            }),
95        }
96    }
97}
98
99impl Serialize for Order {
100    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
101        let (kind, value) = match self {
102            Self::MarketOrder(body) => (
103                "MARKET",
104                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
105            ),
106            Self::LimitOrder(body) => (
107                "LIMIT",
108                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
109            ),
110            Self::StopOrder(body) => (
111                "STOP",
112                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
113            ),
114            Self::MarketIfTouchedOrder(body) => (
115                "MARKET_IF_TOUCHED",
116                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
117            ),
118            Self::TakeProfitOrder(body) => (
119                "TAKE_PROFIT",
120                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
121            ),
122            Self::StopLossOrder(body) => (
123                "STOP_LOSS",
124                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
125            ),
126            Self::GuaranteedStopLossOrder(body) => (
127                "GUARANTEED_STOP_LOSS",
128                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
129            ),
130            Self::TrailingStopLossOrder(body) => (
131                "TRAILING_STOP_LOSS",
132                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
133            ),
134            Self::FixedPriceOrder(body) => (
135                "FIXED_PRICE",
136                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
137            ),
138            Self::Unknown { kind, raw } => (kind.as_str(), raw.clone()),
139        };
140        let value = tagged_value(kind, value).map_err(serde::ser::Error::custom)?;
141        value.serialize(serializer)
142    }
143}
144
145/// Documented `OrderRequest` variants identified by OANDA’s `type` field.
146/// Unknown request variants are rejected rather than forwarded as trading instructions.
147#[derive(Debug, Clone, PartialEq)]
148#[non_exhaustive]
149pub enum OrderRequest {
150    /// `MARKET` provider variant.
151    MarketOrderRequest(MarketOrderRequest),
152    /// `LIMIT` provider variant.
153    LimitOrderRequest(LimitOrderRequest),
154    /// `STOP` provider variant.
155    StopOrderRequest(StopOrderRequest),
156    /// `MARKET_IF_TOUCHED` provider variant.
157    MarketIfTouchedOrderRequest(MarketIfTouchedOrderRequest),
158    /// `TAKE_PROFIT` provider variant.
159    TakeProfitOrderRequest(TakeProfitOrderRequest),
160    /// `STOP_LOSS` provider variant.
161    StopLossOrderRequest(StopLossOrderRequest),
162    /// `GUARANTEED_STOP_LOSS` provider variant.
163    GuaranteedStopLossOrderRequest(GuaranteedStopLossOrderRequest),
164    /// `TRAILING_STOP_LOSS` provider variant.
165    TrailingStopLossOrderRequest(TrailingStopLossOrderRequest),
166}
167
168impl<'de> Deserialize<'de> for OrderRequest {
169    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
170        let value = serde_json::Value::deserialize(deserializer)?;
171        let kind = discriminator(&value).map_err(serde::de::Error::custom)?;
172        match kind {
173            "MARKET" => serde_json::from_value(value)
174                .map(Self::MarketOrderRequest)
175                .map_err(serde::de::Error::custom),
176            "LIMIT" => serde_json::from_value(value)
177                .map(Self::LimitOrderRequest)
178                .map_err(serde::de::Error::custom),
179            "STOP" => serde_json::from_value(value)
180                .map(Self::StopOrderRequest)
181                .map_err(serde::de::Error::custom),
182            "MARKET_IF_TOUCHED" => serde_json::from_value(value)
183                .map(Self::MarketIfTouchedOrderRequest)
184                .map_err(serde::de::Error::custom),
185            "TAKE_PROFIT" => serde_json::from_value(value)
186                .map(Self::TakeProfitOrderRequest)
187                .map_err(serde::de::Error::custom),
188            "STOP_LOSS" => serde_json::from_value(value)
189                .map(Self::StopLossOrderRequest)
190                .map_err(serde::de::Error::custom),
191            "GUARANTEED_STOP_LOSS" => serde_json::from_value(value)
192                .map(Self::GuaranteedStopLossOrderRequest)
193                .map_err(serde::de::Error::custom),
194            "TRAILING_STOP_LOSS" => serde_json::from_value(value)
195                .map(Self::TrailingStopLossOrderRequest)
196                .map_err(serde::de::Error::custom),
197            other => Err(serde::de::Error::custom(format!(
198                "unsupported OANDA request type {other:?}"
199            ))),
200        }
201    }
202}
203
204impl Serialize for OrderRequest {
205    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
206        let (kind, value) = match self {
207            Self::MarketOrderRequest(body) => (
208                "MARKET",
209                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
210            ),
211            Self::LimitOrderRequest(body) => (
212                "LIMIT",
213                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
214            ),
215            Self::StopOrderRequest(body) => (
216                "STOP",
217                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
218            ),
219            Self::MarketIfTouchedOrderRequest(body) => (
220                "MARKET_IF_TOUCHED",
221                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
222            ),
223            Self::TakeProfitOrderRequest(body) => (
224                "TAKE_PROFIT",
225                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
226            ),
227            Self::StopLossOrderRequest(body) => (
228                "STOP_LOSS",
229                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
230            ),
231            Self::GuaranteedStopLossOrderRequest(body) => (
232                "GUARANTEED_STOP_LOSS",
233                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
234            ),
235            Self::TrailingStopLossOrderRequest(body) => (
236                "TRAILING_STOP_LOSS",
237                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
238            ),
239        };
240        let value = tagged_value(kind, value).map_err(serde::ser::Error::custom)?;
241        value.serialize(serializer)
242    }
243}
244
245/// Documented Transaction variants identified by OANDA’s `type` field.
246/// Unknown provider objects retain their discriminator and raw fields for inspection.
247#[derive(Debug, Clone, PartialEq)]
248#[non_exhaustive]
249#[expect(
250    clippy::large_enum_variant,
251    reason = "boxing a variant would change every caller's match; the stream already boxes the whole Transaction"
252)]
253pub enum Transaction {
254    /// `CREATE` provider variant.
255    CreateTransaction(CreateTransaction),
256    /// `CLOSE` provider variant.
257    CloseTransaction(CloseTransaction),
258    /// `REOPEN` provider variant.
259    ReopenTransaction(ReopenTransaction),
260    /// `CLIENT_CONFIGURE` provider variant.
261    ClientConfigureTransaction(ClientConfigureTransaction),
262    /// `CLIENT_CONFIGURE_REJECT` provider variant.
263    ClientConfigureRejectTransaction(ClientConfigureRejectTransaction),
264    /// `TRANSFER_FUNDS` provider variant.
265    TransferFundsTransaction(TransferFundsTransaction),
266    /// `TRANSFER_FUNDS_REJECT` provider variant.
267    TransferFundsRejectTransaction(TransferFundsRejectTransaction),
268    /// `MARKET_ORDER` provider variant.
269    MarketOrderTransaction(MarketOrderTransaction),
270    /// `MARKET_ORDER_REJECT` provider variant.
271    MarketOrderRejectTransaction(MarketOrderRejectTransaction),
272    /// `FIXED_PRICE_ORDER` provider variant.
273    FixedPriceOrderTransaction(FixedPriceOrderTransaction),
274    /// `LIMIT_ORDER` provider variant.
275    LimitOrderTransaction(LimitOrderTransaction),
276    /// `LIMIT_ORDER_REJECT` provider variant.
277    LimitOrderRejectTransaction(LimitOrderRejectTransaction),
278    /// `STOP_ORDER` provider variant.
279    StopOrderTransaction(StopOrderTransaction),
280    /// `STOP_ORDER_REJECT` provider variant.
281    StopOrderRejectTransaction(StopOrderRejectTransaction),
282    /// `MARKET_IF_TOUCHED_ORDER` provider variant.
283    MarketIfTouchedOrderTransaction(MarketIfTouchedOrderTransaction),
284    /// `MARKET_IF_TOUCHED_ORDER_REJECT` provider variant.
285    MarketIfTouchedOrderRejectTransaction(MarketIfTouchedOrderRejectTransaction),
286    /// `TAKE_PROFIT_ORDER` provider variant.
287    TakeProfitOrderTransaction(TakeProfitOrderTransaction),
288    /// `TAKE_PROFIT_ORDER_REJECT` provider variant.
289    TakeProfitOrderRejectTransaction(TakeProfitOrderRejectTransaction),
290    /// `STOP_LOSS_ORDER` provider variant.
291    StopLossOrderTransaction(StopLossOrderTransaction),
292    /// `STOP_LOSS_ORDER_REJECT` provider variant.
293    StopLossOrderRejectTransaction(StopLossOrderRejectTransaction),
294    /// `GUARANTEED_STOP_LOSS_ORDER` provider variant.
295    GuaranteedStopLossOrderTransaction(GuaranteedStopLossOrderTransaction),
296    /// `GUARANTEED_STOP_LOSS_ORDER_REJECT` provider variant.
297    GuaranteedStopLossOrderRejectTransaction(GuaranteedStopLossOrderRejectTransaction),
298    /// `TRAILING_STOP_LOSS_ORDER` provider variant.
299    TrailingStopLossOrderTransaction(TrailingStopLossOrderTransaction),
300    /// `TRAILING_STOP_LOSS_ORDER_REJECT` provider variant.
301    TrailingStopLossOrderRejectTransaction(TrailingStopLossOrderRejectTransaction),
302    /// `ORDER_FILL` provider variant.
303    OrderFillTransaction(OrderFillTransaction),
304    /// `ORDER_CANCEL` provider variant.
305    OrderCancelTransaction(OrderCancelTransaction),
306    /// `ORDER_CANCEL_REJECT` provider variant.
307    OrderCancelRejectTransaction(OrderCancelRejectTransaction),
308    /// `ORDER_CLIENT_EXTENSIONS_MODIFY` provider variant.
309    OrderClientExtensionsModifyTransaction(OrderClientExtensionsModifyTransaction),
310    /// `ORDER_CLIENT_EXTENSIONS_MODIFY_REJECT` provider variant.
311    OrderClientExtensionsModifyRejectTransaction(OrderClientExtensionsModifyRejectTransaction),
312    /// `TRADE_CLIENT_EXTENSIONS_MODIFY` provider variant.
313    TradeClientExtensionsModifyTransaction(TradeClientExtensionsModifyTransaction),
314    /// `TRADE_CLIENT_EXTENSIONS_MODIFY_REJECT` provider variant.
315    TradeClientExtensionsModifyRejectTransaction(TradeClientExtensionsModifyRejectTransaction),
316    /// `MARGIN_CALL_ENTER` provider variant.
317    MarginCallEnterTransaction(MarginCallEnterTransaction),
318    /// `MARGIN_CALL_EXTEND` provider variant.
319    MarginCallExtendTransaction(MarginCallExtendTransaction),
320    /// `MARGIN_CALL_EXIT` provider variant.
321    MarginCallExitTransaction(MarginCallExitTransaction),
322    /// `DELAYED_TRADE_CLOSURE` provider variant.
323    DelayedTradeClosureTransaction(DelayedTradeClosureTransaction),
324    /// `DAILY_FINANCING` provider variant.
325    DailyFinancingTransaction(DailyFinancingTransaction),
326    /// `DIVIDEND_ADJUSTMENT` provider variant.
327    DividendAdjustmentTransaction(DividendAdjustmentTransaction),
328    /// `RESET_RESETTABLE_PL` provider variant.
329    ResetResettablePLTransaction(ResetResettablePLTransaction),
330    /// Future provider variant with its bounded raw value.
331    Unknown {
332        /// Provider discriminator.
333        kind: String,
334        /// Bounded provider object.
335        raw: serde_json::Value,
336    },
337}
338
339impl<'de> Deserialize<'de> for Transaction {
340    #[expect(
341        clippy::too_many_lines,
342        reason = "the exhaustive provider discriminator table keeps response decoding auditable"
343    )]
344    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
345        let value = serde_json::Value::deserialize(deserializer)?;
346        let kind = discriminator(&value).map_err(serde::de::Error::custom)?;
347        match kind {
348            "CREATE" => serde_json::from_value(value)
349                .map(Self::CreateTransaction)
350                .map_err(serde::de::Error::custom),
351            "CLOSE" => serde_json::from_value(value)
352                .map(Self::CloseTransaction)
353                .map_err(serde::de::Error::custom),
354            "REOPEN" => serde_json::from_value(value)
355                .map(Self::ReopenTransaction)
356                .map_err(serde::de::Error::custom),
357            "CLIENT_CONFIGURE" => serde_json::from_value(value)
358                .map(Self::ClientConfigureTransaction)
359                .map_err(serde::de::Error::custom),
360            "CLIENT_CONFIGURE_REJECT" => serde_json::from_value(value)
361                .map(Self::ClientConfigureRejectTransaction)
362                .map_err(serde::de::Error::custom),
363            "TRANSFER_FUNDS" => serde_json::from_value(value)
364                .map(Self::TransferFundsTransaction)
365                .map_err(serde::de::Error::custom),
366            "TRANSFER_FUNDS_REJECT" => serde_json::from_value(value)
367                .map(Self::TransferFundsRejectTransaction)
368                .map_err(serde::de::Error::custom),
369            "MARKET_ORDER" => serde_json::from_value(value)
370                .map(Self::MarketOrderTransaction)
371                .map_err(serde::de::Error::custom),
372            "MARKET_ORDER_REJECT" => serde_json::from_value(value)
373                .map(Self::MarketOrderRejectTransaction)
374                .map_err(serde::de::Error::custom),
375            "FIXED_PRICE_ORDER" => serde_json::from_value(value)
376                .map(Self::FixedPriceOrderTransaction)
377                .map_err(serde::de::Error::custom),
378            "LIMIT_ORDER" => serde_json::from_value(value)
379                .map(Self::LimitOrderTransaction)
380                .map_err(serde::de::Error::custom),
381            "LIMIT_ORDER_REJECT" => serde_json::from_value(value)
382                .map(Self::LimitOrderRejectTransaction)
383                .map_err(serde::de::Error::custom),
384            "STOP_ORDER" => serde_json::from_value(value)
385                .map(Self::StopOrderTransaction)
386                .map_err(serde::de::Error::custom),
387            "STOP_ORDER_REJECT" => serde_json::from_value(value)
388                .map(Self::StopOrderRejectTransaction)
389                .map_err(serde::de::Error::custom),
390            "MARKET_IF_TOUCHED_ORDER" => serde_json::from_value(value)
391                .map(Self::MarketIfTouchedOrderTransaction)
392                .map_err(serde::de::Error::custom),
393            "MARKET_IF_TOUCHED_ORDER_REJECT" => serde_json::from_value(value)
394                .map(Self::MarketIfTouchedOrderRejectTransaction)
395                .map_err(serde::de::Error::custom),
396            "TAKE_PROFIT_ORDER" => serde_json::from_value(value)
397                .map(Self::TakeProfitOrderTransaction)
398                .map_err(serde::de::Error::custom),
399            "TAKE_PROFIT_ORDER_REJECT" => serde_json::from_value(value)
400                .map(Self::TakeProfitOrderRejectTransaction)
401                .map_err(serde::de::Error::custom),
402            "STOP_LOSS_ORDER" => serde_json::from_value(value)
403                .map(Self::StopLossOrderTransaction)
404                .map_err(serde::de::Error::custom),
405            "STOP_LOSS_ORDER_REJECT" => serde_json::from_value(value)
406                .map(Self::StopLossOrderRejectTransaction)
407                .map_err(serde::de::Error::custom),
408            "GUARANTEED_STOP_LOSS_ORDER" => serde_json::from_value(value)
409                .map(Self::GuaranteedStopLossOrderTransaction)
410                .map_err(serde::de::Error::custom),
411            "GUARANTEED_STOP_LOSS_ORDER_REJECT" => serde_json::from_value(value)
412                .map(Self::GuaranteedStopLossOrderRejectTransaction)
413                .map_err(serde::de::Error::custom),
414            "TRAILING_STOP_LOSS_ORDER" => serde_json::from_value(value)
415                .map(Self::TrailingStopLossOrderTransaction)
416                .map_err(serde::de::Error::custom),
417            "TRAILING_STOP_LOSS_ORDER_REJECT" => serde_json::from_value(value)
418                .map(Self::TrailingStopLossOrderRejectTransaction)
419                .map_err(serde::de::Error::custom),
420            "ORDER_FILL" => serde_json::from_value(value)
421                .map(Self::OrderFillTransaction)
422                .map_err(serde::de::Error::custom),
423            "ORDER_CANCEL" => serde_json::from_value(value)
424                .map(Self::OrderCancelTransaction)
425                .map_err(serde::de::Error::custom),
426            "ORDER_CANCEL_REJECT" => serde_json::from_value(value)
427                .map(Self::OrderCancelRejectTransaction)
428                .map_err(serde::de::Error::custom),
429            "ORDER_CLIENT_EXTENSIONS_MODIFY" => serde_json::from_value(value)
430                .map(Self::OrderClientExtensionsModifyTransaction)
431                .map_err(serde::de::Error::custom),
432            "ORDER_CLIENT_EXTENSIONS_MODIFY_REJECT" => serde_json::from_value(value)
433                .map(Self::OrderClientExtensionsModifyRejectTransaction)
434                .map_err(serde::de::Error::custom),
435            "TRADE_CLIENT_EXTENSIONS_MODIFY" => serde_json::from_value(value)
436                .map(Self::TradeClientExtensionsModifyTransaction)
437                .map_err(serde::de::Error::custom),
438            "TRADE_CLIENT_EXTENSIONS_MODIFY_REJECT" => serde_json::from_value(value)
439                .map(Self::TradeClientExtensionsModifyRejectTransaction)
440                .map_err(serde::de::Error::custom),
441            "MARGIN_CALL_ENTER" => serde_json::from_value(value)
442                .map(Self::MarginCallEnterTransaction)
443                .map_err(serde::de::Error::custom),
444            "MARGIN_CALL_EXTEND" => serde_json::from_value(value)
445                .map(Self::MarginCallExtendTransaction)
446                .map_err(serde::de::Error::custom),
447            "MARGIN_CALL_EXIT" => serde_json::from_value(value)
448                .map(Self::MarginCallExitTransaction)
449                .map_err(serde::de::Error::custom),
450            "DELAYED_TRADE_CLOSURE" => serde_json::from_value(value)
451                .map(Self::DelayedTradeClosureTransaction)
452                .map_err(serde::de::Error::custom),
453            "DAILY_FINANCING" => serde_json::from_value(value)
454                .map(Self::DailyFinancingTransaction)
455                .map_err(serde::de::Error::custom),
456            "DIVIDEND_ADJUSTMENT" => serde_json::from_value(value)
457                .map(Self::DividendAdjustmentTransaction)
458                .map_err(serde::de::Error::custom),
459            "RESET_RESETTABLE_PL" => serde_json::from_value(value)
460                .map(Self::ResetResettablePLTransaction)
461                .map_err(serde::de::Error::custom),
462            other => Ok(Self::Unknown {
463                kind: other.to_owned(),
464                raw: value,
465            }),
466        }
467    }
468}
469
470impl Serialize for Transaction {
471    #[expect(
472        clippy::too_many_lines,
473        reason = "the exhaustive provider discriminator table keeps response serialization auditable"
474    )]
475    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
476        let (kind, value) = match self {
477            Self::CreateTransaction(body) => (
478                "CREATE",
479                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
480            ),
481            Self::CloseTransaction(body) => (
482                "CLOSE",
483                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
484            ),
485            Self::ReopenTransaction(body) => (
486                "REOPEN",
487                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
488            ),
489            Self::ClientConfigureTransaction(body) => (
490                "CLIENT_CONFIGURE",
491                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
492            ),
493            Self::ClientConfigureRejectTransaction(body) => (
494                "CLIENT_CONFIGURE_REJECT",
495                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
496            ),
497            Self::TransferFundsTransaction(body) => (
498                "TRANSFER_FUNDS",
499                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
500            ),
501            Self::TransferFundsRejectTransaction(body) => (
502                "TRANSFER_FUNDS_REJECT",
503                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
504            ),
505            Self::MarketOrderTransaction(body) => (
506                "MARKET_ORDER",
507                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
508            ),
509            Self::MarketOrderRejectTransaction(body) => (
510                "MARKET_ORDER_REJECT",
511                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
512            ),
513            Self::FixedPriceOrderTransaction(body) => (
514                "FIXED_PRICE_ORDER",
515                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
516            ),
517            Self::LimitOrderTransaction(body) => (
518                "LIMIT_ORDER",
519                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
520            ),
521            Self::LimitOrderRejectTransaction(body) => (
522                "LIMIT_ORDER_REJECT",
523                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
524            ),
525            Self::StopOrderTransaction(body) => (
526                "STOP_ORDER",
527                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
528            ),
529            Self::StopOrderRejectTransaction(body) => (
530                "STOP_ORDER_REJECT",
531                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
532            ),
533            Self::MarketIfTouchedOrderTransaction(body) => (
534                "MARKET_IF_TOUCHED_ORDER",
535                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
536            ),
537            Self::MarketIfTouchedOrderRejectTransaction(body) => (
538                "MARKET_IF_TOUCHED_ORDER_REJECT",
539                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
540            ),
541            Self::TakeProfitOrderTransaction(body) => (
542                "TAKE_PROFIT_ORDER",
543                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
544            ),
545            Self::TakeProfitOrderRejectTransaction(body) => (
546                "TAKE_PROFIT_ORDER_REJECT",
547                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
548            ),
549            Self::StopLossOrderTransaction(body) => (
550                "STOP_LOSS_ORDER",
551                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
552            ),
553            Self::StopLossOrderRejectTransaction(body) => (
554                "STOP_LOSS_ORDER_REJECT",
555                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
556            ),
557            Self::GuaranteedStopLossOrderTransaction(body) => (
558                "GUARANTEED_STOP_LOSS_ORDER",
559                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
560            ),
561            Self::GuaranteedStopLossOrderRejectTransaction(body) => (
562                "GUARANTEED_STOP_LOSS_ORDER_REJECT",
563                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
564            ),
565            Self::TrailingStopLossOrderTransaction(body) => (
566                "TRAILING_STOP_LOSS_ORDER",
567                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
568            ),
569            Self::TrailingStopLossOrderRejectTransaction(body) => (
570                "TRAILING_STOP_LOSS_ORDER_REJECT",
571                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
572            ),
573            Self::OrderFillTransaction(body) => (
574                "ORDER_FILL",
575                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
576            ),
577            Self::OrderCancelTransaction(body) => (
578                "ORDER_CANCEL",
579                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
580            ),
581            Self::OrderCancelRejectTransaction(body) => (
582                "ORDER_CANCEL_REJECT",
583                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
584            ),
585            Self::OrderClientExtensionsModifyTransaction(body) => (
586                "ORDER_CLIENT_EXTENSIONS_MODIFY",
587                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
588            ),
589            Self::OrderClientExtensionsModifyRejectTransaction(body) => (
590                "ORDER_CLIENT_EXTENSIONS_MODIFY_REJECT",
591                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
592            ),
593            Self::TradeClientExtensionsModifyTransaction(body) => (
594                "TRADE_CLIENT_EXTENSIONS_MODIFY",
595                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
596            ),
597            Self::TradeClientExtensionsModifyRejectTransaction(body) => (
598                "TRADE_CLIENT_EXTENSIONS_MODIFY_REJECT",
599                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
600            ),
601            Self::MarginCallEnterTransaction(body) => (
602                "MARGIN_CALL_ENTER",
603                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
604            ),
605            Self::MarginCallExtendTransaction(body) => (
606                "MARGIN_CALL_EXTEND",
607                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
608            ),
609            Self::MarginCallExitTransaction(body) => (
610                "MARGIN_CALL_EXIT",
611                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
612            ),
613            Self::DelayedTradeClosureTransaction(body) => (
614                "DELAYED_TRADE_CLOSURE",
615                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
616            ),
617            Self::DailyFinancingTransaction(body) => (
618                "DAILY_FINANCING",
619                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
620            ),
621            Self::DividendAdjustmentTransaction(body) => (
622                "DIVIDEND_ADJUSTMENT",
623                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
624            ),
625            Self::ResetResettablePLTransaction(body) => (
626                "RESET_RESETTABLE_PL",
627                serde_json::to_value(body).map_err(serde::ser::Error::custom)?,
628            ),
629            Self::Unknown { kind, raw } => (kind.as_str(), raw.clone()),
630        };
631        let value = tagged_value(kind, value).map_err(serde::ser::Error::custom)?;
632        value.serialize(serializer)
633    }
634}
635
636fn discriminator(value: &serde_json::Value) -> Result<&str, String> {
637    match value.get("type") {
638        None => Err("missing OANDA type discriminator".to_owned()),
639        Some(serde_json::Value::String(kind)) => Ok(kind),
640        Some(observed) => Err(format!(
641            "OANDA type discriminator must be a string; observed {observed}"
642        )),
643    }
644}
645
646fn tagged_value(kind: &str, value: serde_json::Value) -> Result<serde_json::Value, String> {
647    let value = {
648        let mut value = value;
649        let object = value
650            .as_object_mut()
651            .ok_or_else(|| "OANDA variant must be an object".to_owned())?;
652        if let Some(observed) = object.get("type")
653            && observed.as_str() != Some(kind)
654        {
655            return Err(format!(
656                "OANDA type discriminator mismatch: expected {kind:?}, observed {observed}"
657            ));
658        }
659        object.insert(
660            "type".to_owned(),
661            serde_json::Value::String(kind.to_owned()),
662        );
663        value
664    };
665    Ok(value)
666}
667
668impl crate::validation::Validate for OrderRequest {
669    fn validate(&self) -> crate::Result<()> {
670        match self {
671            Self::MarketOrderRequest(body) => body.validate(),
672            Self::LimitOrderRequest(body) => body.validate(),
673            Self::StopOrderRequest(body) => body.validate(),
674            Self::MarketIfTouchedOrderRequest(body) => body.validate(),
675            Self::TakeProfitOrderRequest(body) => body.validate(),
676            Self::StopLossOrderRequest(body) => body.validate(),
677            Self::GuaranteedStopLossOrderRequest(body) => body.validate(),
678            Self::TrailingStopLossOrderRequest(body) => body.validate(),
679        }
680    }
681}