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
366impl TryFrom<&[u8]> for PositionFilterProto {
367    type Error = Error;
368
369    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
370        PositionFilterProto::decode(bytes).map_err(|_| Error::DeserializationError)
371    }
372}
373
374impl TryFrom<Vec<u8>> for PositionFilterProto {
375    type Error = Error;
376
377    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
378        PositionFilterProto::decode(bytes.as_slice()).map_err(|_| Error::DeserializationError)
379    }
380}
381
382#[cfg(test)]
383mod test {
384    use super::*;
385    use crate::fintekkers::models::position::{field_map_entry, PositionFilterOperator};
386    use crate::fintekkers::models::util::DecimalValueProto;
387    use crate::fintekkers::models::util::UuidProto;
388    use crate::fintekkers::wrappers::models::utils::uuid_wrapper::UUIDWrapper;
389    use chrono::naive::NaiveDate;
390    use chrono::{DateTime, Datelike};
391    use chrono_tz::Tz;
392    use prost_types::Any;
393    use uuid::Uuid;
394
395    // Helper function to create a UUID field entry
396    fn create_uuid_field_entry(field: FieldProto, uuid_str: &str) -> FieldMapEntry {
397        let uuid = Uuid::parse_str(uuid_str).unwrap();
398        let uuid_proto = UuidProto {
399            raw_uuid: uuid.as_bytes().to_vec(),
400        };
401        let mut uuid_bytes = Vec::new();
402        uuid_proto.encode(&mut uuid_bytes).unwrap();
403
404        let packed_uuid = Any {
405            type_url: "type.googleapis.com/fintekkers.models.util.UUIDProto".to_string(),
406            value: uuid_bytes,
407        };
408
409        FieldMapEntry {
410            field: field as i32,
411            operator: 0,
412            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
413                packed_uuid,
414            )),
415        }
416    }
417
418    // Helper function to create a date field entry
419    fn create_date_field_entry(
420        field: FieldProto,
421        year: u32,
422        month: u32,
423        day: u32,
424    ) -> FieldMapEntry {
425        let date_proto = LocalDateProto { year, month, day };
426        let mut date_bytes = Vec::new();
427        date_proto.encode(&mut date_bytes).unwrap();
428
429        let packed_date = Any {
430            type_url: "type.googleapis.com/fintekkers.models.util.LocalDateProto".to_string(),
431            value: date_bytes,
432        };
433
434        FieldMapEntry {
435            field: field as i32,
436            operator: 0,
437            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
438                packed_date,
439            )),
440        }
441    }
442
443    // Helper function to create a timestamp field entry
444    fn create_timestamp_field_entry() -> FieldMapEntry {
445        let timestamp = prost_types::Timestamp {
446            seconds: 1609459200, // 2021-01-01 00:00:00 UTC
447            nanos: 0,
448        };
449        let timestamp_proto = LocalTimestampProto {
450            timestamp: Some(timestamp),
451            time_zone: "America/New_York".to_string(), // Use a specific timezone to test
452        };
453        let mut timestamp_bytes = Vec::new();
454        timestamp_proto.encode(&mut timestamp_bytes).unwrap();
455
456        let packed_timestamp = Any {
457            type_url: "type.googleapis.com/fintekkers.models.util.LocalTimestampProto".to_string(),
458            value: timestamp_bytes,
459        };
460
461        FieldMapEntry {
462            field: FieldProto::AsOf as i32,
463            operator: 0,
464            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
465                packed_timestamp,
466            )),
467        }
468    }
469
470    // Helper function to create a string field entry
471    fn create_string_field_entry(field: FieldProto, value: &str) -> FieldMapEntry {
472        FieldMapEntry {
473            field: field as i32,
474            operator: 0,
475            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::StringValue(
476                value.to_string(),
477            )),
478        }
479    }
480
481    // Helper function to create an enum field entry
482    fn create_enum_field_entry(field: FieldProto, enum_value: i32) -> FieldMapEntry {
483        FieldMapEntry {
484            field: field as i32,
485            operator: 0,
486            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::EnumValue(
487                enum_value,
488            )),
489        }
490    }
491
492    // Helper function to create a measure entry
493    fn create_measure_entry(measure: MeasureProto, value: &str) -> MeasureMapEntry {
494        MeasureMapEntry {
495            measure: measure as i32,
496            measure_decimal_value: Some(DecimalValueProto {
497                arbitrary_precision_value: value.to_string(),
498            }),
499        }
500    }
501
502    #[test]
503    fn test_position_filter_with_security_id_uuid() {
504        // Create a UUID
505        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
506        let uuid_wrapper = UUIDWrapper::new(UuidProto {
507            raw_uuid: uuid.as_bytes().to_vec(),
508        });
509
510        // Convert UUID to UuidProto and encode it
511        let uuid_proto: UuidProto = uuid_wrapper.into();
512        let mut uuid_bytes = Vec::new();
513        uuid_proto.encode(&mut uuid_bytes).unwrap();
514
515        // Create an Any with the packed UUID
516        let packed_uuid = Any {
517            type_url: "type.googleapis.com/fintekkers.models.util.UUIDProto".to_string(),
518            value: uuid_bytes,
519        };
520
521        // Create a FieldMapEntry with SECURITY_ID field and the packed UUID
522        let field_entry = FieldMapEntry {
523            field: FieldProto::SecurityId as i32,
524            operator: PositionFilterOperator::Equals as i32,
525            field_map_value_one_of: Some(field_map_entry::FieldMapValueOneOf::FieldValuePacked(
526                packed_uuid,
527            )),
528        };
529
530        // Create PositionFilterProto with the filter
531        let filter_proto = PositionFilterProto {
532            object_class: "PositionFilter".to_string(),
533            version: "0.0.1".to_string(),
534            filters: vec![field_entry],
535        };
536
537        // Create PositionFilter
538        let position_filter = PositionFilter::new(filter_proto.clone());
539
540        // Verify the filter was created correctly
541        let filters = position_filter.get_filters();
542        assert_eq!(filters.len(), 1);
543        assert_eq!(filters[0].field, FieldProto::SecurityId as i32);
544        assert_eq!(filters[0].operator, PositionFilterOperator::Equals as i32);
545
546        // Verify we can get the proto back
547        let retrieved_proto = position_filter.to_proto();
548        assert_eq!(retrieved_proto.object_class, "PositionFilter");
549        assert_eq!(retrieved_proto.version, "0.0.1");
550        assert_eq!(retrieved_proto.filters.len(), 1);
551    }
552
553    #[test]
554    fn test_position_with_string_field() {
555        let field_entry = create_string_field_entry(FieldProto::PortfolioName, "Test Portfolio");
556        let position_proto = PositionProto {
557            object_class: "Position".to_string(),
558            version: "0.0.1".to_string(),
559            position_view: 0,
560            position_type: 0,
561            measures: vec![],
562            fields: vec![field_entry.clone()],
563        };
564
565        let position = Position::new(position_proto);
566
567        // Test get_field_value
568        let value = position.get_field_value(FieldProto::PortfolioName).unwrap();
569        let string_value = value.downcast_ref::<String>().unwrap();
570        assert_eq!(string_value, "Test Portfolio");
571
572        // Test get_field
573        let field_value = position.get_field(field_entry.clone()).unwrap();
574        let string_value = field_value.downcast_ref::<String>().unwrap();
575        assert_eq!(string_value, "Test Portfolio");
576
577        // Test get_field_display
578        let display = position.get_field_display(field_entry).unwrap();
579        assert_eq!(display, "Test Portfolio");
580    }
581
582    #[test]
583    fn test_position_with_uuid_fields() {
584        let id_entry =
585            create_uuid_field_entry(FieldProto::Id, "550e8400-e29b-41d4-a716-446655440000");
586        let security_id_entry = create_uuid_field_entry(
587            FieldProto::SecurityId,
588            "660e8400-e29b-41d4-a716-446655440001",
589        );
590        let portfolio_id_entry = create_uuid_field_entry(
591            FieldProto::PortfolioId,
592            "770e8400-e29b-41d4-a716-446655440002",
593        );
594
595        let position_proto = PositionProto {
596            object_class: "Position".to_string(),
597            version: "0.0.1".to_string(),
598            position_view: 0,
599            position_type: 0,
600            measures: vec![],
601            fields: vec![
602                id_entry.clone(),
603                security_id_entry.clone(),
604                portfolio_id_entry.clone(),
605            ],
606        };
607
608        let position = Position::new(position_proto);
609
610        // Test Id field - should return native Rust Uuid
611        let value = position.get_field_value(FieldProto::Id).unwrap();
612        let uuid = value.downcast_ref::<Uuid>().unwrap();
613        assert_eq!(uuid.to_string(), "550e8400-e29b-41d4-a716-446655440000");
614
615        // Test SecurityId field
616        let value = position.get_field_value(FieldProto::SecurityId).unwrap();
617        let uuid = value.downcast_ref::<Uuid>().unwrap();
618        assert_eq!(uuid.to_string(), "660e8400-e29b-41d4-a716-446655440001");
619
620        // Test PortfolioId field
621        let value = position.get_field_value(FieldProto::PortfolioId).unwrap();
622        let uuid = value.downcast_ref::<Uuid>().unwrap();
623        assert_eq!(uuid.to_string(), "770e8400-e29b-41d4-a716-446655440002");
624    }
625
626    #[test]
627    fn test_position_with_date_fields() {
628        let trade_date_entry = create_date_field_entry(FieldProto::TradeDate, 2023, 10, 15);
629        let maturity_date_entry = create_date_field_entry(FieldProto::MaturityDate, 2025, 12, 31);
630        let settlement_date_entry =
631            create_date_field_entry(FieldProto::SettlementDate, 2023, 10, 17);
632
633        let position_proto = PositionProto {
634            object_class: "Position".to_string(),
635            version: "0.0.1".to_string(),
636            position_view: 0,
637            position_type: 0,
638            measures: vec![],
639            fields: vec![
640                trade_date_entry.clone(),
641                maturity_date_entry.clone(),
642                settlement_date_entry.clone(),
643            ],
644        };
645
646        let position = Position::new(position_proto);
647
648        // Test TradeDate - should return native Rust NaiveDate
649        let value = position.get_field_value(FieldProto::TradeDate).unwrap();
650        let date = value.downcast_ref::<NaiveDate>().unwrap();
651        assert_eq!(date.year(), 2023);
652        assert_eq!(date.month(), 10);
653        assert_eq!(date.day(), 15);
654
655        // Test get_field_display for date
656        let display = position.get_field_display(trade_date_entry).unwrap();
657        assert_eq!(display, "2023-10-15");
658
659        // Test MaturityDate
660        let value = position.get_field_value(FieldProto::MaturityDate).unwrap();
661        let date = value.downcast_ref::<NaiveDate>().unwrap();
662        assert_eq!(date.year(), 2025);
663        assert_eq!(date.month(), 12);
664        assert_eq!(date.day(), 31);
665    }
666
667    #[test]
668    fn test_position_with_timestamp_field() {
669        let as_of_entry = create_timestamp_field_entry();
670
671        let position_proto = PositionProto {
672            object_class: "Position".to_string(),
673            version: "0.0.1".to_string(),
674            position_view: 0,
675            position_type: 0,
676            measures: vec![],
677            fields: vec![as_of_entry.clone()],
678        };
679
680        let position = Position::new(position_proto);
681
682        // Test AsOf field - should return native Rust DateTime<Tz> with correct timezone
683        let value = position.get_field_value(FieldProto::AsOf).unwrap();
684        let timestamp = value.downcast_ref::<DateTime<Tz>>().unwrap();
685
686        // The timestamp is 2021-01-01 00:00:00 UTC, which is 2020-12-31 19:00:00 EST
687        // So in EST timezone (America/New_York), it should be Dec 31, 2020
688        assert_eq!(timestamp.year(), 2020);
689        assert_eq!(timestamp.month(), 12);
690        assert_eq!(timestamp.day(), 31);
691
692        // Verify the timezone is America/New_York (EST/EDT)
693        let timezone_str = timestamp.timezone().to_string();
694        assert!(
695            timezone_str.contains("EST")
696                || timezone_str.contains("EDT")
697                || timezone_str.contains("America/New_York")
698        );
699
700        // Test get_field_display for timestamp
701        let display = position.get_field_display(as_of_entry).unwrap();
702        assert!(display.contains("2020")); // Should contain the year (EST is 5 hours behind UTC)
703    }
704
705    #[test]
706    fn test_position_with_enum_field() {
707        // TransactionType enum value (assuming 1 = BUY, 2 = SELL, etc.)
708        let transaction_type_entry = create_enum_field_entry(FieldProto::TransactionType, 1);
709
710        let position_proto = PositionProto {
711            object_class: "Position".to_string(),
712            version: "0.0.1".to_string(),
713            position_view: 0,
714            position_type: 0,
715            measures: vec![],
716            fields: vec![transaction_type_entry.clone()],
717        };
718
719        let position = Position::new(position_proto);
720
721        // Test enum field
722        let value = position
723            .get_field_value(FieldProto::TransactionType)
724            .unwrap();
725        let enum_value = value.downcast_ref::<i32>().unwrap();
726        assert_eq!(*enum_value, 1);
727
728        // Test get_field_display for enum
729        let display = position.get_field_display(transaction_type_entry).unwrap();
730        assert_eq!(display, "1");
731    }
732
733    #[test]
734    fn test_position_with_decimal_measures() {
735        let directed_quantity = create_measure_entry(MeasureProto::DirectedQuantity, "100.50");
736        let market_value = create_measure_entry(MeasureProto::MarketValue, "50000.75");
737
738        let position_proto = PositionProto {
739            object_class: "Position".to_string(),
740            version: "0.0.1".to_string(),
741            position_view: 0,
742            position_type: 0,
743            measures: vec![directed_quantity.clone(), market_value.clone()],
744            fields: vec![],
745        };
746
747        let position = Position::new(position_proto);
748
749        // Test get_measure_value - should return native Rust Decimal
750        let measure = position
751            .get_measure_value(MeasureProto::DirectedQuantity)
752            .unwrap();
753        assert_eq!(measure.to_string(), "100.50");
754
755        // Test get_measure
756        let measure = position.get_measure(directed_quantity).unwrap();
757        assert_eq!(measure.to_string(), "100.50");
758
759        // Test MarketValue
760        let measure = position
761            .get_measure_value(MeasureProto::MarketValue)
762            .unwrap();
763        assert_eq!(measure.to_string(), "50000.75");
764    }
765
766    #[test]
767    fn test_position_get_fields_and_measures() {
768        let field1 = create_string_field_entry(FieldProto::PortfolioName, "Portfolio 1");
769        let field2 =
770            create_uuid_field_entry(FieldProto::Id, "550e8400-e29b-41d4-a716-446655440000");
771        let measure1 = create_measure_entry(MeasureProto::DirectedQuantity, "100.0");
772
773        let position_proto = PositionProto {
774            object_class: "Position".to_string(),
775            version: "0.0.1".to_string(),
776            position_view: 0,
777            position_type: 0,
778            measures: vec![measure1],
779            fields: vec![field1, field2],
780        };
781
782        let position = Position::new(position_proto);
783
784        // Test get_fields
785        let fields = position.get_fields();
786        assert_eq!(fields.len(), 2);
787
788        // Test get_measures
789        let measures = position.get_measures();
790        assert_eq!(measures.len(), 1);
791    }
792
793    #[test]
794    fn test_position_to_string() {
795        let field1 = create_string_field_entry(FieldProto::PortfolioName, "Test Portfolio");
796        let field2 = create_date_field_entry(FieldProto::TradeDate, 2023, 10, 15);
797        let measure1 = create_measure_entry(MeasureProto::DirectedQuantity, "100.50");
798
799        let position_proto = PositionProto {
800            object_class: "Position".to_string(),
801            version: "0.0.1".to_string(),
802            position_view: 0,
803            position_type: 0,
804            measures: vec![measure1],
805            fields: vec![field1, field2],
806        };
807
808        let position = Position::new(position_proto);
809
810        let result = position.to_string();
811        assert!(result.contains("PORTFOLIO_NAME"));
812        assert!(result.contains("Test Portfolio"));
813        assert!(result.contains("TRADE_DATE"));
814        assert!(result.contains("2023-10-15"));
815        assert!(result.contains("DIRECTED_QUANTITY"));
816        assert!(result.contains("100.50"));
817    }
818
819    #[test]
820    fn test_position_field_not_found() {
821        let position_proto = PositionProto {
822            object_class: "Position".to_string(),
823            version: "0.0.1".to_string(),
824            position_view: 0,
825            position_type: 0,
826            measures: vec![],
827            fields: vec![],
828        };
829
830        let position = Position::new(position_proto);
831
832        // Test get_field_value for non-existent field
833        let result = position.get_field_value(FieldProto::PortfolioName);
834        assert!(result.is_err());
835        assert_eq!(result.unwrap_err(), "Could not find field in position");
836
837        // Test get_measure_value for non-existent measure
838        let result = position.get_measure_value(MeasureProto::DirectedQuantity);
839        assert!(result.is_err());
840        assert_eq!(result.unwrap_err(), "Could not find measure in position");
841    }
842
843    #[test]
844    fn test_position_with_multiple_field_types() {
845        // Test a position with various field types
846        let string_field = create_string_field_entry(FieldProto::AssetClass, "Equity");
847        let uuid_field = create_uuid_field_entry(
848            FieldProto::SecurityId,
849            "880e8400-e29b-41d4-a716-446655440003",
850        );
851        let date_field = create_date_field_entry(FieldProto::MaturityDate, 2024, 6, 30);
852        let enum_field = create_enum_field_entry(FieldProto::PositionStatus, 1);
853        let timestamp_field = create_timestamp_field_entry();
854
855        let position_proto = PositionProto {
856            object_class: "Position".to_string(),
857            version: "0.0.1".to_string(),
858            position_view: 0,
859            position_type: 0,
860            measures: vec![
861                create_measure_entry(MeasureProto::DirectedQuantity, "50.25"),
862                create_measure_entry(MeasureProto::MarketValue, "25000.00"),
863            ],
864            fields: vec![
865                string_field.clone(),
866                uuid_field.clone(),
867                date_field.clone(),
868                enum_field.clone(),
869                timestamp_field.clone(),
870            ],
871        };
872
873        let position = Position::new(position_proto);
874
875        // Verify all fields can be retrieved
876        assert!(position.get_field_value(FieldProto::AssetClass).is_ok());
877        assert!(position.get_field_value(FieldProto::SecurityId).is_ok());
878        assert!(position.get_field_value(FieldProto::MaturityDate).is_ok());
879        assert!(position.get_field_value(FieldProto::PositionStatus).is_ok());
880        assert!(position.get_field_value(FieldProto::AsOf).is_ok());
881
882        // Verify measures can be retrieved
883        assert!(position
884            .get_measure_value(MeasureProto::DirectedQuantity)
885            .is_ok());
886        assert!(position
887            .get_measure_value(MeasureProto::MarketValue)
888            .is_ok());
889
890        // Verify get_field_display works for all types
891        assert!(position.get_field_display(string_field).is_ok());
892        assert!(position.get_field_display(uuid_field).is_ok());
893        assert!(position.get_field_display(date_field).is_ok());
894        assert!(position.get_field_display(enum_field).is_ok());
895        assert!(position.get_field_display(timestamp_field).is_ok());
896    }
897}
898
899// ...