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 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 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 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 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 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 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 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 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 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 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 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 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(×tamp_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 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 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 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 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 fn create_timestamp_field_entry() -> FieldMapEntry {
445 let timestamp = prost_types::Timestamp {
446 seconds: 1609459200, nanos: 0,
448 };
449 let timestamp_proto = LocalTimestampProto {
450 timestamp: Some(timestamp),
451 time_zone: "America/New_York".to_string(), };
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 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 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 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 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 let uuid_proto: UuidProto = uuid_wrapper.into();
512 let mut uuid_bytes = Vec::new();
513 uuid_proto.encode(&mut uuid_bytes).unwrap();
514
515 let packed_uuid = Any {
517 type_url: "type.googleapis.com/fintekkers.models.util.UUIDProto".to_string(),
518 value: uuid_bytes,
519 };
520
521 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 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 let position_filter = PositionFilter::new(filter_proto.clone());
539
540 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 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 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 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 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 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 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 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 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 let display = position.get_field_display(trade_date_entry).unwrap();
657 assert_eq!(display, "2023-10-15");
658
659 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 let value = position.get_field_value(FieldProto::AsOf).unwrap();
684 let timestamp = value.downcast_ref::<DateTime<Tz>>().unwrap();
685
686 assert_eq!(timestamp.year(), 2020);
689 assert_eq!(timestamp.month(), 12);
690 assert_eq!(timestamp.day(), 31);
691
692 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 let display = position.get_field_display(as_of_entry).unwrap();
702 assert!(display.contains("2020")); }
704
705 #[test]
706 fn test_position_with_enum_field() {
707 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 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 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 let measure = position
751 .get_measure_value(MeasureProto::DirectedQuantity)
752 .unwrap();
753 assert_eq!(measure.to_string(), "100.50");
754
755 let measure = position.get_measure(directed_quantity).unwrap();
757 assert_eq!(measure.to_string(), "100.50");
758
759 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 let fields = position.get_fields();
786 assert_eq!(fields.len(), 2);
787
788 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 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 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 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 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 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 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