Skip to main content

ledger_models/fintekkers/wrappers/models/
position.rs

1use crate::fintekkers::models::portfolio::PortfolioProto;
2use crate::fintekkers::models::position::{
3    field_map_entry, FieldMapEntry, FieldProto, MeasureMapEntry, MeasureProto, PositionFilterProto,
4    PositionProto,
5};
6use crate::fintekkers::models::price::PriceProto;
7use crate::fintekkers::models::security::{IdentifierProto, SecurityProto, TenorProto};
8use crate::fintekkers::models::strategy::StrategyProto;
9use crate::fintekkers::models::util::{LocalDateProto, LocalTimestampProto, UuidProto};
10use crate::fintekkers::wrappers::models::portfolio::PortfolioWrapper;
11use crate::fintekkers::wrappers::models::security::SecurityWrapper;
12use crate::fintekkers::wrappers::models::utils::errors::Error;
13use crate::fintekkers::wrappers::models::utils::serialization::ProtoSerializationUtil;
14use chrono::naive::NaiveDate;
15use chrono::DateTime;
16use chrono_tz::Tz;
17use prost::Message;
18use prost_types::Any;
19use rust_decimal::Decimal;
20use uuid::Uuid;
21
22#[derive(Debug)]
23pub struct Position {
24    position_proto: PositionProto,
25}
26
27impl Position {
28    pub fn new(position_proto: PositionProto) -> Position {
29        Position { position_proto }
30    }
31
32    pub fn get_field_value(
33        &self,
34        field: FieldProto,
35    ) -> Result<Box<dyn std::any::Any>, &'static str> {
36        let field_entry = FieldMapEntry {
37            field: field as i32,
38            operator: 0,
39            field_map_value_one_of: None,
40        };
41        self.get_field(field_entry)
42    }
43
44    pub fn get_field(
45        &self,
46        field_to_get: FieldMapEntry,
47    ) -> Result<Box<dyn std::any::Any>, &'static str> {
48        for tmp_field in &self.position_proto.fields {
49            if tmp_field.field == field_to_get.field {
50                // Check for string value first
51                if let Some(field_map_entry::FieldMapValueOneOf::StringValue(ref s)) =
52                    tmp_field.field_map_value_one_of
53                {
54                    if !s.is_empty() {
55                        return Ok(Box::new(s.clone()));
56                    }
57                }
58
59                // Check for enum value
60                if let Some(field_map_entry::FieldMapValueOneOf::EnumValue(enum_val)) =
61                    tmp_field.field_map_value_one_of
62                {
63                    return Ok(Box::new(enum_val));
64                }
65
66                // Handle packed values
67                if let Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(_)) =
68                    tmp_field.field_map_value_one_of
69                {
70                    let field_enum = FieldProto::from_i32(field_to_get.field)
71                        .ok_or("Invalid field enum value")?;
72
73                    // Special handling for Portfolio and Security - return wrappers
74                    if field_enum == FieldProto::Portfolio {
75                        if let Ok(proto) =
76                            Self::unpack_field(tmp_field)?.downcast::<PortfolioProto>()
77                        {
78                            return Ok(Box::new(PortfolioWrapper::new(*proto)));
79                        }
80                    }
81
82                    if field_enum == FieldProto::Security
83                        || field_enum == FieldProto::CashImpactSecurity
84                    {
85                        if let Ok(proto) =
86                            Self::unpack_field(tmp_field)?.downcast::<SecurityProto>()
87                        {
88                            return Ok(Box::new(SecurityWrapper::new(*proto)));
89                        }
90                    }
91
92                    // For other fields, return the unpacked value
93                    return Self::unpack_field(tmp_field);
94                }
95            }
96        }
97        Err("Could not find field in position")
98    }
99
100    pub fn get_measure_value(&self, measure: MeasureProto) -> Result<Decimal, &'static str> {
101        let measure_entry = MeasureMapEntry {
102            measure: measure as i32,
103            measure_decimal_value: None,
104        };
105        self.get_measure(measure_entry)
106    }
107
108    pub fn get_measure(&self, measure_to_get: MeasureMapEntry) -> Result<Decimal, &'static str> {
109        for tmp_measure in &self.position_proto.measures {
110            if tmp_measure.measure == measure_to_get.measure {
111                return Self::unpack_measure(tmp_measure);
112            }
113        }
114        Err("Could not find measure in position")
115    }
116
117    pub fn get_field_display(&self, field_to_get: FieldMapEntry) -> Result<String, &'static str> {
118        let value = self.get_field(field_to_get)?;
119
120        // Try to downcast_ref to different types and format them
121        if let Some(s) = value.downcast_ref::<String>() {
122            return Ok(s.clone());
123        }
124
125        if let Some(i) = value.downcast_ref::<i32>() {
126            return Ok(i.to_string());
127        }
128
129        // For wrapper types, use their Display implementation
130        if let Some(portfolio) = value.downcast_ref::<PortfolioWrapper>() {
131            return Ok(portfolio.portfolio_name().to_string());
132        }
133
134        if let Some(_security) = value.downcast_ref::<SecurityWrapper>() {
135            // SecurityWrapper doesn't implement Debug, so use a simple representation
136            return Ok("Security".to_string());
137        }
138
139        if let Some(uuid) = value.downcast_ref::<Uuid>() {
140            return Ok(uuid.to_string());
141        }
142
143        if let Some(date) = value.downcast_ref::<NaiveDate>() {
144            return Ok(date.to_string());
145        }
146
147        if let Some(timestamp) = value.downcast_ref::<DateTime<Tz>>() {
148            return Ok(timestamp.to_string());
149        }
150
151        if let Some(price_proto) = value.downcast_ref::<PriceProto>() {
152            if let Some(ref uuid_proto) = price_proto.uuid {
153                let uuid = ProtoSerializationUtil::deserialize_uuid(uuid_proto)
154                    .map_err(|_| "Failed to convert UuidProto to Uuid")?;
155                return Ok(uuid.to_string());
156            }
157        }
158
159        // Default: try to format as debug string
160        Ok("Unknown field type".to_string())
161    }
162
163    pub fn get_measures(&self) -> Vec<MeasureMapEntry> {
164        self.position_proto.measures.clone()
165    }
166
167    pub fn get_fields(&self) -> Vec<FieldMapEntry> {
168        self.position_proto.fields.clone()
169    }
170
171    pub fn to_string(&self) -> String {
172        let mut output = String::new();
173
174        for field in self.get_fields() {
175            let field_name = FieldProto::from_i32(field.field)
176                .map(|f| f.as_str_name())
177                .unwrap_or("UNKNOWN");
178            output.push_str(field_name);
179            output.push(',');
180
181            if let Ok(display) = self.get_field_display(field) {
182                output.push_str(&display);
183            }
184            output.push(';');
185        }
186
187        for measure in self.get_measures() {
188            // MeasureProto doesn't have from_i32, so we'll use the numeric value
189            // or try to match it manually
190            let measure_name = match measure.measure {
191                0 => "UNKNOWN_MEASURE",
192                1 => "DIRECTED_QUANTITY",
193                2 => "MARKET_VALUE",
194                3 => "UNADJUSTED_COST_BASIS",
195                4 => "ADJUSTED_COST_BASIS",
196                5 => "CURRENT_YIELD",
197                6 => "YIELD_TO_MATURITY",
198                _ => "UNKNOWN",
199            };
200            output.push_str(measure_name);
201            output.push(',');
202
203            if let Ok(decimal) = self.get_measure(measure) {
204                output.push_str(&decimal.to_string());
205            }
206            output.push(';');
207        }
208
209        output
210    }
211
212    fn wrap_string_to_any(my_string: &str) -> prost_types::Any {
213        // StringValue wrapper encoding - for now return a placeholder
214        // This would need proper StringValue proto support
215        Any {
216            type_url: "type.googleapis.com/google.protobuf.StringValue".to_string(),
217            value: my_string.as_bytes().to_vec(),
218        }
219    }
220
221    fn pack_field(field_to_pack: &dyn std::any::Any) -> Result<prost_types::Any, &'static str> {
222        // Try to downcast to LocalDateProto
223        if let Some(date_proto) = field_to_pack.downcast_ref::<LocalDateProto>() {
224            let mut bytes = Vec::new();
225            date_proto
226                .encode(&mut bytes)
227                .map_err(|_| "Failed to encode LocalDateProto")?;
228            return Ok(Any {
229                type_url: "type.googleapis.com/fintekkers.models.util.LocalDateProto".to_string(),
230                value: bytes,
231            });
232        }
233
234        Err("Unsupported field type for packing")
235    }
236
237    fn unpack_field(
238        field_to_unpack: &FieldMapEntry,
239    ) -> Result<Box<dyn std::any::Any>, &'static str> {
240        let packed_value = match &field_to_unpack.field_map_value_one_of {
241            Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(any)) => any,
242            _ => return Err("Field value is required"),
243        };
244
245        let binary_value = &packed_value.value;
246        let field_enum =
247            FieldProto::from_i32(field_to_unpack.field).ok_or("Invalid field enum value")?;
248
249        match field_enum {
250            FieldProto::PortfolioId
251            | FieldProto::SecurityId
252            | FieldProto::PriceId
253            | FieldProto::Id => {
254                let uuid_proto = UuidProto::decode(binary_value.as_slice())
255                    .map_err(|_| "Failed to decode UUIDProto")?;
256                let uuid = ProtoSerializationUtil::deserialize_uuid(&uuid_proto)
257                    .map_err(|_| "Failed to convert UuidProto to Uuid")?;
258                Ok(Box::new(uuid))
259            }
260            FieldProto::AsOf => {
261                let timestamp_proto = LocalTimestampProto::decode(binary_value.as_slice())
262                    .map_err(|_| "Failed to decode LocalTimestampProto")?;
263                let timestamp = ProtoSerializationUtil::deserialize_timestamp(&timestamp_proto)
264                    .map_err(|_| "Failed to convert LocalTimestampProto to DateTime")?;
265                Ok(Box::new(timestamp))
266            }
267            FieldProto::TradeDate
268            | FieldProto::MaturityDate
269            | FieldProto::IssueDate
270            | FieldProto::SettlementDate
271            | FieldProto::TaxLotOpenDate
272            | FieldProto::TaxLotCloseDate
273            | FieldProto::EffectiveDate => {
274                let date_proto = LocalDateProto::decode(binary_value.as_slice())
275                    .map_err(|_| "Failed to decode LocalDateProto")?;
276                let date = ProtoSerializationUtil::deserialize_date(&date_proto)
277                    .map_err(|_| "Failed to convert LocalDateProto to NaiveDate")?;
278                Ok(Box::new(date))
279            }
280            FieldProto::Identifier => {
281                let identifier_proto = IdentifierProto::decode(binary_value.as_slice())
282                    .map_err(|_| "Failed to decode IdentifierProto")?;
283                Ok(Box::new(identifier_proto))
284            }
285            FieldProto::Strategy => {
286                let strategy_proto = StrategyProto::decode(binary_value.as_slice())
287                    .map_err(|_| "Failed to decode StrategyProto")?;
288                Ok(Box::new(strategy_proto))
289            }
290            FieldProto::Tenor | FieldProto::AdjustedTenor => {
291                let tenor_proto = TenorProto::decode(binary_value.as_slice())
292                    .map_err(|_| "Failed to decode TenorProto")?;
293                Ok(Box::new(tenor_proto))
294            }
295            FieldProto::Price => {
296                let price_proto = PriceProto::decode(binary_value.as_slice())
297                    .map_err(|_| "Failed to decode PriceProto")?;
298                Ok(Box::new(price_proto))
299            }
300            FieldProto::PortfolioName
301            | FieldProto::SecurityDescription
302            | FieldProto::SecurityIssuerName
303            | FieldProto::ProductType
304            | FieldProto::ProductClass
305            | FieldProto::AssetClass => {
306                // StringValue is a wrapper, but we can decode it manually
307                // For now, try to decode as a simple string representation
308                // In practice, these might be stored as StringValue in the Any
309                // We'll need to handle the StringValue wrapper properly
310                // For now, return an error and note this needs proper StringValue handling
311                Err("StringValue decoding not yet implemented - needs prost_types StringValue support")
312            }
313            FieldProto::Portfolio => {
314                let portfolio_proto = PortfolioProto::decode(binary_value.as_slice())
315                    .map_err(|_| "Failed to decode PortfolioProto")?;
316                Ok(Box::new(portfolio_proto))
317            }
318            FieldProto::Security | FieldProto::CashImpactSecurity => {
319                let security_proto = SecurityProto::decode(binary_value.as_slice())
320                    .map_err(|_| "Failed to decode SecurityProto")?;
321                Ok(Box::new(security_proto))
322            }
323            FieldProto::TransactionType | FieldProto::PositionStatus => {
324                // These are enum values, return the enum value directly
325                if let Some(field_map_entry::FieldMapValueOneOf::EnumValue(val)) =
326                    &field_to_unpack.field_map_value_one_of
327                {
328                    Ok(Box::new(*val))
329                } else {
330                    Err("Enum value expected for TransactionType or PositionStatus")
331                }
332            }
333            _ => Err("Field not found. Could not unpack field. Mapping missing"),
334        }
335    }
336
337    fn unpack_measure(measure_to_unpack: &MeasureMapEntry) -> Result<Decimal, &'static str> {
338        let decimal_proto = measure_to_unpack
339            .measure_decimal_value
340            .as_ref()
341            .ok_or("Measure decimal value is required")?;
342        ProtoSerializationUtil::deserialize_decimal(decimal_proto)
343            .map_err(|_| "Failed to convert DecimalValueProto to Decimal")
344    }
345}
346
347#[derive(Debug)]
348pub struct PositionFilter {
349    filter_proto: PositionFilterProto,
350}
351
352impl PositionFilter {
353    pub fn new(filter_proto: PositionFilterProto) -> PositionFilter {
354        PositionFilter { filter_proto }
355    }
356
357    pub fn get_filters(&self) -> &[FieldMapEntry] {
358        &self.filter_proto.filters
359    }
360
361    pub fn to_proto(&self) -> PositionFilterProto {
362        self.filter_proto.clone()
363    }
364
365    /// Find the first filter entry matching the given field.
366    pub fn find_filter(&self, field: FieldProto) -> Option<&FieldMapEntry> {
367        self.filter_proto
368            .filters
369            .iter()
370            .find(|f| f.field == field as i32)
371    }
372
373    /// Extract a UUID value from a filter entry for UUID-typed fields
374    /// (SECURITY_ID, PORTFOLIO_ID, PRICE_ID, ID).
375    ///
376    /// This is the primary method the price-service and other Rust services
377    /// should use to extract a security UUID from a PositionFilter.
378    pub fn get_uuid(&self, field: FieldProto) -> Result<Uuid, &'static str> {
379        let entry = self
380            .find_filter(field)
381            .ok_or("Field not found in filter")?;
382
383        let packed_value = match &entry.field_map_value_one_of {
384            Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(any)) => any,
385            _ => return Err("Expected packed value for UUID field"),
386        };
387
388        let uuid_proto = UuidProto::decode(packed_value.value.as_slice())
389            .map_err(|_| "Failed to decode UUIDProto")?;
390        ProtoSerializationUtil::deserialize_uuid(&uuid_proto)
391            .map_err(|_| "Failed to convert UuidProto to Uuid")
392    }
393
394    /// Extract a string value from a filter entry for string-typed fields
395    /// (ASSET_CLASS, PORTFOLIO_NAME, SECURITY_DESCRIPTION, etc.).
396    pub fn get_string(&self, field: FieldProto) -> Result<String, &'static str> {
397        let entry = self
398            .find_filter(field)
399            .ok_or("Field not found in filter")?;
400
401        match &entry.field_map_value_one_of {
402            Some(field_map_entry::FieldMapValueOneOf::StringValue(s)) => Ok(s.clone()),
403            _ => Err("Expected string value for this field"),
404        }
405    }
406
407    /// Extract a date value from a filter entry for date-typed fields
408    /// (TRADE_DATE, MATURITY_DATE, ISSUE_DATE, SETTLEMENT_DATE, etc.).
409    pub fn get_date(&self, field: FieldProto) -> Result<NaiveDate, &'static str> {
410        let entry = self
411            .find_filter(field)
412            .ok_or("Field not found in filter")?;
413
414        let packed_value = match &entry.field_map_value_one_of {
415            Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(any)) => any,
416            _ => return Err("Expected packed value for date field"),
417        };
418
419        let date_proto = LocalDateProto::decode(packed_value.value.as_slice())
420            .map_err(|_| "Failed to decode LocalDateProto")?;
421        ProtoSerializationUtil::deserialize_date(&date_proto)
422            .map_err(|_| "Failed to convert LocalDateProto to NaiveDate")
423    }
424
425    /// Extract an enum value from a filter entry for enum-typed fields
426    /// (TRANSACTION_TYPE, POSITION_STATUS).
427    pub fn get_enum_value(&self, field: FieldProto) -> Result<i32, &'static str> {
428        let entry = self
429            .find_filter(field)
430            .ok_or("Field not found in filter")?;
431
432        match &entry.field_map_value_one_of {
433            Some(field_map_entry::FieldMapValueOneOf::EnumValue(val)) => Ok(*val),
434            _ => Err("Expected enum value for this field"),
435        }
436    }
437
438    /// Extract a timestamp value from a filter entry (AS_OF field).
439    pub fn get_timestamp(&self, field: FieldProto) -> Result<DateTime<Tz>, &'static str> {
440        let entry = self
441            .find_filter(field)
442            .ok_or("Field not found in filter")?;
443
444        let packed_value = match &entry.field_map_value_one_of {
445            Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(any)) => any,
446            _ => return Err("Expected packed value for timestamp field"),
447        };
448
449        let timestamp_proto = LocalTimestampProto::decode(packed_value.value.as_slice())
450            .map_err(|_| "Failed to decode LocalTimestampProto")?;
451        ProtoSerializationUtil::deserialize_timestamp(&timestamp_proto)
452            .map_err(|_| "Failed to convert LocalTimestampProto to DateTime")
453    }
454}
455
456impl TryFrom<&[u8]> for PositionFilterProto {
457    type Error = Error;
458
459    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
460        PositionFilterProto::decode(bytes).map_err(|_| Error::DeserializationError)
461    }
462}
463
464impl TryFrom<Vec<u8>> for PositionFilterProto {
465    type Error = Error;
466
467    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
468        PositionFilterProto::decode(bytes.as_slice()).map_err(|_| Error::DeserializationError)
469    }
470}
471
472#[cfg(test)]
473mod test {
474    use super::*;
475    use crate::fintekkers::models::position::{field_map_entry, PositionFilterOperator};
476    use crate::fintekkers::models::util::DecimalValueProto;
477    use crate::fintekkers::models::util::UuidProto;
478    use crate::fintekkers::wrappers::models::utils::uuid_wrapper::UUIDWrapper;
479    use chrono::naive::NaiveDate;
480    use chrono::{DateTime, Datelike};
481    use chrono_tz::Tz;
482    use prost_types::Any;
483    use uuid::Uuid;
484
485    // Helper function to create a UUID field entry
486    fn create_uuid_field_entry(field: FieldProto, uuid_str: &str) -> FieldMapEntry {
487        let uuid = Uuid::parse_str(uuid_str).unwrap();
488        let uuid_proto = UuidProto {
489            raw_uuid: uuid.as_bytes().to_vec(),
490        };
491        let mut uuid_bytes = Vec::new();
492        uuid_proto.encode(&mut uuid_bytes).unwrap();
493
494        let packed_uuid = Any {
495            type_url: "type.googleapis.com/fintekkers.models.util.UUIDProto".to_string(),
496            value: uuid_bytes,
497        };
498
499        FieldMapEntry {
500            field: field as i32,
501            operator: 0,
502            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
503                packed_uuid,
504            )),
505        }
506    }
507
508    // Helper function to create a date field entry
509    fn create_date_field_entry(
510        field: FieldProto,
511        year: u32,
512        month: u32,
513        day: u32,
514    ) -> FieldMapEntry {
515        let date_proto = LocalDateProto { year, month, day };
516        let mut date_bytes = Vec::new();
517        date_proto.encode(&mut date_bytes).unwrap();
518
519        let packed_date = Any {
520            type_url: "type.googleapis.com/fintekkers.models.util.LocalDateProto".to_string(),
521            value: date_bytes,
522        };
523
524        FieldMapEntry {
525            field: field as i32,
526            operator: 0,
527            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
528                packed_date,
529            )),
530        }
531    }
532
533    // Helper function to create a timestamp field entry
534    fn create_timestamp_field_entry() -> FieldMapEntry {
535        let timestamp = prost_types::Timestamp {
536            seconds: 1609459200, // 2021-01-01 00:00:00 UTC
537            nanos: 0,
538        };
539        let timestamp_proto = LocalTimestampProto {
540            timestamp: Some(timestamp),
541            time_zone: "America/New_York".to_string(), // Use a specific timezone to test
542        };
543        let mut timestamp_bytes = Vec::new();
544        timestamp_proto.encode(&mut timestamp_bytes).unwrap();
545
546        let packed_timestamp = Any {
547            type_url: "type.googleapis.com/fintekkers.models.util.LocalTimestampProto".to_string(),
548            value: timestamp_bytes,
549        };
550
551        FieldMapEntry {
552            field: FieldProto::AsOf as i32,
553            operator: 0,
554            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
555                packed_timestamp,
556            )),
557        }
558    }
559
560    // Helper function to create a string field entry
561    fn create_string_field_entry(field: FieldProto, value: &str) -> FieldMapEntry {
562        FieldMapEntry {
563            field: field as i32,
564            operator: 0,
565            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::StringValue(
566                value.to_string(),
567            )),
568        }
569    }
570
571    // Helper function to create an enum field entry
572    fn create_enum_field_entry(field: FieldProto, enum_value: i32) -> FieldMapEntry {
573        FieldMapEntry {
574            field: field as i32,
575            operator: 0,
576            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::EnumValue(
577                enum_value,
578            )),
579        }
580    }
581
582    // Helper function to create a measure entry
583    fn create_measure_entry(measure: MeasureProto, value: &str) -> MeasureMapEntry {
584        MeasureMapEntry {
585            measure: measure as i32,
586            measure_decimal_value: Some(DecimalValueProto {
587                arbitrary_precision_value: value.to_string(),
588            }),
589        }
590    }
591
592    #[test]
593    fn test_position_filter_with_security_id_uuid() {
594        // Create a UUID
595        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
596        let uuid_wrapper = UUIDWrapper::new(UuidProto {
597            raw_uuid: uuid.as_bytes().to_vec(),
598        });
599
600        // Convert UUID to UuidProto and encode it
601        let uuid_proto: UuidProto = uuid_wrapper.into();
602        let mut uuid_bytes = Vec::new();
603        uuid_proto.encode(&mut uuid_bytes).unwrap();
604
605        // Create an Any with the packed UUID
606        let packed_uuid = Any {
607            type_url: "type.googleapis.com/fintekkers.models.util.UUIDProto".to_string(),
608            value: uuid_bytes,
609        };
610
611        // Create a FieldMapEntry with SECURITY_ID field and the packed UUID
612        let field_entry = FieldMapEntry {
613            field: FieldProto::SecurityId as i32,
614            operator: PositionFilterOperator::Equals as i32,
615            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
616                packed_uuid,
617            )),
618        };
619
620        // Create PositionFilterProto with the filter
621        let filter_proto = PositionFilterProto {
622            object_class: "PositionFilter".to_string(),
623            version: "0.0.1".to_string(),
624            filters: vec![field_entry],
625        };
626
627        // Create PositionFilter
628        let position_filter = PositionFilter::new(filter_proto.clone());
629
630        // Verify the filter was created correctly
631        let filters = position_filter.get_filters();
632        assert_eq!(filters.len(), 1);
633        assert_eq!(filters[0].field, FieldProto::SecurityId as i32);
634        assert_eq!(filters[0].operator, PositionFilterOperator::Equals as i32);
635
636        // Verify we can get the proto back
637        let retrieved_proto = position_filter.to_proto();
638        assert_eq!(retrieved_proto.object_class, "PositionFilter");
639        assert_eq!(retrieved_proto.version, "0.0.1");
640        assert_eq!(retrieved_proto.filters.len(), 1);
641    }
642
643    #[test]
644    fn test_position_with_string_field() {
645        let field_entry = create_string_field_entry(FieldProto::PortfolioName, "Test Portfolio");
646        let position_proto = PositionProto {
647            object_class: "Position".to_string(),
648            version: "0.0.1".to_string(),
649            position_view: 0,
650            position_type: 0,
651            measures: vec![],
652            reporting_currency: None,
653            fields: vec![field_entry.clone()],
654        };
655
656        let position = Position::new(position_proto);
657
658        // Test get_field_value
659        let value = position.get_field_value(FieldProto::PortfolioName).unwrap();
660        let string_value = value.downcast_ref::<String>().unwrap();
661        assert_eq!(string_value, "Test Portfolio");
662
663        // Test get_field
664        let field_value = position.get_field(field_entry.clone()).unwrap();
665        let string_value = field_value.downcast_ref::<String>().unwrap();
666        assert_eq!(string_value, "Test Portfolio");
667
668        // Test get_field_display
669        let display = position.get_field_display(field_entry).unwrap();
670        assert_eq!(display, "Test Portfolio");
671    }
672
673    #[test]
674    fn test_position_with_uuid_fields() {
675        let id_entry =
676            create_uuid_field_entry(FieldProto::Id, "550e8400-e29b-41d4-a716-446655440000");
677        let security_id_entry = create_uuid_field_entry(
678            FieldProto::SecurityId,
679            "660e8400-e29b-41d4-a716-446655440001",
680        );
681        let portfolio_id_entry = create_uuid_field_entry(
682            FieldProto::PortfolioId,
683            "770e8400-e29b-41d4-a716-446655440002",
684        );
685
686        let position_proto = PositionProto {
687            object_class: "Position".to_string(),
688            version: "0.0.1".to_string(),
689            position_view: 0,
690            position_type: 0,
691            measures: vec![],
692            reporting_currency: None,
693            fields: vec![
694                id_entry.clone(),
695                security_id_entry.clone(),
696                portfolio_id_entry.clone(),
697            ],
698        };
699
700        let position = Position::new(position_proto);
701
702        // Test Id field - should return native Rust Uuid
703        let value = position.get_field_value(FieldProto::Id).unwrap();
704        let uuid = value.downcast_ref::<Uuid>().unwrap();
705        assert_eq!(uuid.to_string(), "550e8400-e29b-41d4-a716-446655440000");
706
707        // Test SecurityId field
708        let value = position.get_field_value(FieldProto::SecurityId).unwrap();
709        let uuid = value.downcast_ref::<Uuid>().unwrap();
710        assert_eq!(uuid.to_string(), "660e8400-e29b-41d4-a716-446655440001");
711
712        // Test PortfolioId field
713        let value = position.get_field_value(FieldProto::PortfolioId).unwrap();
714        let uuid = value.downcast_ref::<Uuid>().unwrap();
715        assert_eq!(uuid.to_string(), "770e8400-e29b-41d4-a716-446655440002");
716    }
717
718    #[test]
719    fn test_position_with_date_fields() {
720        let trade_date_entry = create_date_field_entry(FieldProto::TradeDate, 2023, 10, 15);
721        let maturity_date_entry = create_date_field_entry(FieldProto::MaturityDate, 2025, 12, 31);
722        let settlement_date_entry =
723            create_date_field_entry(FieldProto::SettlementDate, 2023, 10, 17);
724
725        let position_proto = PositionProto {
726            object_class: "Position".to_string(),
727            version: "0.0.1".to_string(),
728            position_view: 0,
729            position_type: 0,
730            measures: vec![],
731            reporting_currency: None,
732            fields: vec![
733                trade_date_entry.clone(),
734                maturity_date_entry.clone(),
735                settlement_date_entry.clone(),
736            ],
737        };
738
739        let position = Position::new(position_proto);
740
741        // Test TradeDate - should return native Rust NaiveDate
742        let value = position.get_field_value(FieldProto::TradeDate).unwrap();
743        let date = value.downcast_ref::<NaiveDate>().unwrap();
744        assert_eq!(date.year(), 2023);
745        assert_eq!(date.month(), 10);
746        assert_eq!(date.day(), 15);
747
748        // Test get_field_display for date
749        let display = position.get_field_display(trade_date_entry).unwrap();
750        assert_eq!(display, "2023-10-15");
751
752        // Test MaturityDate
753        let value = position.get_field_value(FieldProto::MaturityDate).unwrap();
754        let date = value.downcast_ref::<NaiveDate>().unwrap();
755        assert_eq!(date.year(), 2025);
756        assert_eq!(date.month(), 12);
757        assert_eq!(date.day(), 31);
758    }
759
760    #[test]
761    fn test_position_with_timestamp_field() {
762        let as_of_entry = create_timestamp_field_entry();
763
764        let position_proto = PositionProto {
765            object_class: "Position".to_string(),
766            version: "0.0.1".to_string(),
767            position_view: 0,
768            position_type: 0,
769            measures: vec![],
770            reporting_currency: None,
771            fields: vec![as_of_entry.clone()],
772        };
773
774        let position = Position::new(position_proto);
775
776        // Test AsOf field - should return native Rust DateTime<Tz> with correct timezone
777        let value = position.get_field_value(FieldProto::AsOf).unwrap();
778        let timestamp = value.downcast_ref::<DateTime<Tz>>().unwrap();
779
780        // The timestamp is 2021-01-01 00:00:00 UTC, which is 2020-12-31 19:00:00 EST
781        // So in EST timezone (America/New_York), it should be Dec 31, 2020
782        assert_eq!(timestamp.year(), 2020);
783        assert_eq!(timestamp.month(), 12);
784        assert_eq!(timestamp.day(), 31);
785
786        // Verify the timezone is America/New_York (EST/EDT)
787        let timezone_str = timestamp.timezone().to_string();
788        assert!(
789            timezone_str.contains("EST")
790                || timezone_str.contains("EDT")
791                || timezone_str.contains("America/New_York")
792        );
793
794        // Test get_field_display for timestamp
795        let display = position.get_field_display(as_of_entry).unwrap();
796        assert!(display.contains("2020")); // Should contain the year (EST is 5 hours behind UTC)
797    }
798
799    #[test]
800    fn test_position_with_enum_field() {
801        // TransactionType enum value (assuming 1 = BUY, 2 = SELL, etc.)
802        let transaction_type_entry = create_enum_field_entry(FieldProto::TransactionType, 1);
803
804        let position_proto = PositionProto {
805            object_class: "Position".to_string(),
806            version: "0.0.1".to_string(),
807            position_view: 0,
808            position_type: 0,
809            measures: vec![],
810            reporting_currency: None,
811            fields: vec![transaction_type_entry.clone()],
812        };
813
814        let position = Position::new(position_proto);
815
816        // Test enum field
817        let value = position
818            .get_field_value(FieldProto::TransactionType)
819            .unwrap();
820        let enum_value = value.downcast_ref::<i32>().unwrap();
821        assert_eq!(*enum_value, 1);
822
823        // Test get_field_display for enum
824        let display = position.get_field_display(transaction_type_entry).unwrap();
825        assert_eq!(display, "1");
826    }
827
828    #[test]
829    fn test_position_with_decimal_measures() {
830        let directed_quantity = create_measure_entry(MeasureProto::DirectedQuantity, "100.50");
831        let market_value = create_measure_entry(MeasureProto::MarketValue, "50000.75");
832
833        let position_proto = PositionProto {
834            object_class: "Position".to_string(),
835            version: "0.0.1".to_string(),
836            position_view: 0,
837            position_type: 0,
838            measures: vec![directed_quantity.clone(), market_value.clone()],
839            reporting_currency: None,
840            fields: vec![],
841        };
842
843        let position = Position::new(position_proto);
844
845        // Test get_measure_value - should return native Rust Decimal
846        let measure = position
847            .get_measure_value(MeasureProto::DirectedQuantity)
848            .unwrap();
849        assert_eq!(measure.to_string(), "100.50");
850
851        // Test get_measure
852        let measure = position.get_measure(directed_quantity).unwrap();
853        assert_eq!(measure.to_string(), "100.50");
854
855        // Test MarketValue
856        let measure = position
857            .get_measure_value(MeasureProto::MarketValue)
858            .unwrap();
859        assert_eq!(measure.to_string(), "50000.75");
860    }
861
862    #[test]
863    fn test_position_get_fields_and_measures() {
864        let field1 = create_string_field_entry(FieldProto::PortfolioName, "Portfolio 1");
865        let field2 =
866            create_uuid_field_entry(FieldProto::Id, "550e8400-e29b-41d4-a716-446655440000");
867        let measure1 = create_measure_entry(MeasureProto::DirectedQuantity, "100.0");
868
869        let position_proto = PositionProto {
870            object_class: "Position".to_string(),
871            version: "0.0.1".to_string(),
872            position_view: 0,
873            position_type: 0,
874            measures: vec![measure1],
875            reporting_currency: None,
876            fields: vec![field1, field2],
877        };
878
879        let position = Position::new(position_proto);
880
881        // Test get_fields
882        let fields = position.get_fields();
883        assert_eq!(fields.len(), 2);
884
885        // Test get_measures
886        let measures = position.get_measures();
887        assert_eq!(measures.len(), 1);
888    }
889
890    #[test]
891    fn test_position_to_string() {
892        let field1 = create_string_field_entry(FieldProto::PortfolioName, "Test Portfolio");
893        let field2 = create_date_field_entry(FieldProto::TradeDate, 2023, 10, 15);
894        let measure1 = create_measure_entry(MeasureProto::DirectedQuantity, "100.50");
895
896        let position_proto = PositionProto {
897            object_class: "Position".to_string(),
898            version: "0.0.1".to_string(),
899            position_view: 0,
900            position_type: 0,
901            measures: vec![measure1],
902            reporting_currency: None,
903            fields: vec![field1, field2],
904        };
905
906        let position = Position::new(position_proto);
907
908        let result = position.to_string();
909        assert!(result.contains("PORTFOLIO_NAME"));
910        assert!(result.contains("Test Portfolio"));
911        assert!(result.contains("TRADE_DATE"));
912        assert!(result.contains("2023-10-15"));
913        assert!(result.contains("DIRECTED_QUANTITY"));
914        assert!(result.contains("100.50"));
915    }
916
917    #[test]
918    fn test_position_field_not_found() {
919        let position_proto = PositionProto {
920            object_class: "Position".to_string(),
921            version: "0.0.1".to_string(),
922            position_view: 0,
923            position_type: 0,
924            measures: vec![],
925            reporting_currency: None,
926            fields: vec![],
927        };
928
929        let position = Position::new(position_proto);
930
931        // Test get_field_value for non-existent field
932        let result = position.get_field_value(FieldProto::PortfolioName);
933        assert!(result.is_err());
934        assert_eq!(result.unwrap_err(), "Could not find field in position");
935
936        // Test get_measure_value for non-existent measure
937        let result = position.get_measure_value(MeasureProto::DirectedQuantity);
938        assert!(result.is_err());
939        assert_eq!(result.unwrap_err(), "Could not find measure in position");
940    }
941
942    #[test]
943    fn test_position_filter_get_uuid() {
944        let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
945        let field_entry = create_uuid_field_entry(FieldProto::SecurityId, uuid_str);
946        let filter = PositionFilter::new(PositionFilterProto {
947            object_class: "PositionFilter".to_string(),
948            version: "0.0.1".to_string(),
949            filters: vec![field_entry],
950        });
951
952        let uuid = filter.get_uuid(FieldProto::SecurityId).unwrap();
953        assert_eq!(uuid.to_string(), uuid_str);
954    }
955
956    #[test]
957    fn test_position_filter_get_uuid_not_found() {
958        let filter = PositionFilter::new(PositionFilterProto {
959            object_class: "PositionFilter".to_string(),
960            version: "0.0.1".to_string(),
961            filters: vec![],
962        });
963
964        let result = filter.get_uuid(FieldProto::SecurityId);
965        assert!(result.is_err());
966        assert_eq!(result.unwrap_err(), "Field not found in filter");
967    }
968
969    #[test]
970    fn test_position_filter_get_string() {
971        let field_entry = create_string_field_entry(FieldProto::AssetClass, "Fixed Income");
972        let filter = PositionFilter::new(PositionFilterProto {
973            object_class: "PositionFilter".to_string(),
974            version: "0.0.1".to_string(),
975            filters: vec![field_entry],
976        });
977
978        let value = filter.get_string(FieldProto::AssetClass).unwrap();
979        assert_eq!(value, "Fixed Income");
980    }
981
982    #[test]
983    fn test_position_filter_get_date() {
984        let field_entry = create_date_field_entry(FieldProto::MaturityDate, 2030, 6, 15);
985        let filter = PositionFilter::new(PositionFilterProto {
986            object_class: "PositionFilter".to_string(),
987            version: "0.0.1".to_string(),
988            filters: vec![field_entry],
989        });
990
991        let date = filter.get_date(FieldProto::MaturityDate).unwrap();
992        assert_eq!(date.year(), 2030);
993        assert_eq!(date.month(), 6);
994        assert_eq!(date.day(), 15);
995    }
996
997    #[test]
998    fn test_position_filter_get_enum_value() {
999        let field_entry = create_enum_field_entry(FieldProto::TransactionType, 1); // BUY
1000        let filter = PositionFilter::new(PositionFilterProto {
1001            object_class: "PositionFilter".to_string(),
1002            version: "0.0.1".to_string(),
1003            filters: vec![field_entry],
1004        });
1005
1006        let value = filter.get_enum_value(FieldProto::TransactionType).unwrap();
1007        assert_eq!(value, 1);
1008    }
1009
1010    #[test]
1011    fn test_position_filter_multiple_filters() {
1012        let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
1013        let uuid_entry = create_uuid_field_entry(FieldProto::SecurityId, uuid_str);
1014        let string_entry = create_string_field_entry(FieldProto::AssetClass, "Fixed Income");
1015        let date_entry = create_date_field_entry(FieldProto::MaturityDate, 2030, 1, 15);
1016
1017        let filter = PositionFilter::new(PositionFilterProto {
1018            object_class: "PositionFilter".to_string(),
1019            version: "0.0.1".to_string(),
1020            filters: vec![uuid_entry, string_entry, date_entry],
1021        });
1022
1023        // All three should be extractable
1024        assert_eq!(
1025            filter.get_uuid(FieldProto::SecurityId).unwrap().to_string(),
1026            uuid_str
1027        );
1028        assert_eq!(
1029            filter.get_string(FieldProto::AssetClass).unwrap(),
1030            "Fixed Income"
1031        );
1032        assert_eq!(filter.get_date(FieldProto::MaturityDate).unwrap().year(), 2030);
1033    }
1034
1035    #[test]
1036    fn test_position_with_multiple_field_types() {
1037        // Test a position with various field types
1038        let string_field = create_string_field_entry(FieldProto::AssetClass, "Equity");
1039        let uuid_field = create_uuid_field_entry(
1040            FieldProto::SecurityId,
1041            "880e8400-e29b-41d4-a716-446655440003",
1042        );
1043        let date_field = create_date_field_entry(FieldProto::MaturityDate, 2024, 6, 30);
1044        let enum_field = create_enum_field_entry(FieldProto::PositionStatus, 1);
1045        let timestamp_field = create_timestamp_field_entry();
1046
1047        let position_proto = PositionProto {
1048            object_class: "Position".to_string(),
1049            version: "0.0.1".to_string(),
1050            position_view: 0,
1051            position_type: 0,
1052            measures: vec![
1053                create_measure_entry(MeasureProto::DirectedQuantity, "50.25"),
1054                create_measure_entry(MeasureProto::MarketValue, "25000.00"),
1055            ],
1056            reporting_currency: None,
1057            fields: vec![
1058                string_field.clone(),
1059                uuid_field.clone(),
1060                date_field.clone(),
1061                enum_field.clone(),
1062                timestamp_field.clone(),
1063            ],
1064        };
1065
1066        let position = Position::new(position_proto);
1067
1068        // Verify all fields can be retrieved
1069        assert!(position.get_field_value(FieldProto::AssetClass).is_ok());
1070        assert!(position.get_field_value(FieldProto::SecurityId).is_ok());
1071        assert!(position.get_field_value(FieldProto::MaturityDate).is_ok());
1072        assert!(position.get_field_value(FieldProto::PositionStatus).is_ok());
1073        assert!(position.get_field_value(FieldProto::AsOf).is_ok());
1074
1075        // Verify measures can be retrieved
1076        assert!(position
1077            .get_measure_value(MeasureProto::DirectedQuantity)
1078            .is_ok());
1079        assert!(position
1080            .get_measure_value(MeasureProto::MarketValue)
1081            .is_ok());
1082
1083        // Verify get_field_display works for all types
1084        assert!(position.get_field_display(string_field).is_ok());
1085        assert!(position.get_field_display(uuid_field).is_ok());
1086        assert!(position.get_field_display(date_field).is_ok());
1087        assert!(position.get_field_display(enum_field).is_ok());
1088        assert!(position.get_field_display(timestamp_field).is_ok());
1089    }
1090}
1091
1092// ...