1use dapi_grpc::platform::v0::{
4 self as proto,
5 get_contested_resource_vote_state_request::{
6 self, get_contested_resource_vote_state_request_v0,
7 },
8 get_contested_resources_request::{
9 self, get_contested_resources_request_v0, GetContestedResourcesRequestV0,
10 },
11 get_vote_polls_by_end_date_request::{self},
12 GetContestedResourceIdentityVotesRequest, GetContestedResourceVoteStateRequest,
13 GetContestedResourceVotersForIdentityRequest, GetContestedResourcesRequest,
14 GetPrefundedSpecializedBalanceRequest, GetVotePollsByEndDateRequest,
15};
16use dpp::{
17 identifier::Identifier, platform_value::Value,
18 voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll,
19};
20use drive::query::{
21 contested_resource_votes_given_by_identity_query::ContestedResourceVotesGivenByIdentityQuery,
22 vote_poll_contestant_votes_query::ContestedDocumentVotePollVotesDriveQuery,
23 vote_poll_vote_state_query::{
24 ContestedDocumentVotePollDriveQuery, ContestedDocumentVotePollDriveQueryResultType,
25 },
26 vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery,
27 VotePollsByEndDateDriveQuery,
28};
29
30use crate::Error;
31
32const BINCODE_CONFIG: dpp::bincode::config::Configuration = dpp::bincode::config::standard();
33
34pub trait TryFromRequest<T>: Sized {
42 fn try_from_request(grpc_request: T) -> Result<Self, Error>;
44
45 fn try_to_request(&self) -> Result<T, Error>;
47}
48
49impl TryFromRequest<get_contested_resource_vote_state_request_v0::ResultType>
50 for ContestedDocumentVotePollDriveQueryResultType
51{
52 fn try_from_request(
53 grpc_request: get_contested_resource_vote_state_request_v0::ResultType,
54 ) -> Result<Self, Error> {
55 use get_contested_resource_vote_state_request_v0::ResultType as GrpcResultType;
56 use ContestedDocumentVotePollDriveQueryResultType as DriveResultType;
57
58 Ok(match grpc_request {
59 GrpcResultType::Documents => DriveResultType::Documents,
60 GrpcResultType::DocumentsAndVoteTally => DriveResultType::DocumentsAndVoteTally,
61 GrpcResultType::VoteTally => DriveResultType::VoteTally,
62 })
63 }
64 fn try_to_request(
65 &self,
66 ) -> Result<get_contested_resource_vote_state_request_v0::ResultType, Error> {
67 use get_contested_resource_vote_state_request_v0::ResultType as GrpcResultType;
68 use ContestedDocumentVotePollDriveQueryResultType as DriveResultType;
69
70 Ok(match self {
71 DriveResultType::Documents => GrpcResultType::Documents,
72 DriveResultType::DocumentsAndVoteTally => GrpcResultType::DocumentsAndVoteTally,
73 DriveResultType::VoteTally => GrpcResultType::VoteTally,
74 DriveResultType::SingleDocumentByContender(_) => {
75 return Err(Error::RequestError {
76 error: "can not perform a single document by contender query remotely"
77 .to_string(),
78 })
79 }
80 })
81 }
82}
83
84impl TryFromRequest<GetContestedResourceVoteStateRequest> for ContestedDocumentVotePollDriveQuery {
85 fn try_from_request(grpc_request: GetContestedResourceVoteStateRequest) -> Result<Self, Error> {
86 let result = match grpc_request.version.ok_or(Error::EmptyVersion)? {
87 get_contested_resource_vote_state_request::Version::V0(v) => {
88 ContestedDocumentVotePollDriveQuery {
89 limit: v.count.map(|v| v as u16),
90 vote_poll: ContestedDocumentResourceVotePoll {
91 contract_id: Identifier::from_bytes(&v.contract_id).map_err(|e| {
92 Error::RequestError {
93 error: format!("cannot decode contract id: {}", e),
94 }
95 })?,
96 document_type_name: v.document_type_name.clone(),
97 index_name: v.index_name.clone(),
98 index_values: bincode_decode_values(v.index_values.iter())?,
99 },
100 result_type: match v.result_type() {
101 get_contested_resource_vote_state_request_v0::ResultType::Documents => {
102 ContestedDocumentVotePollDriveQueryResultType::Documents
103 }
104 get_contested_resource_vote_state_request_v0::ResultType::DocumentsAndVoteTally => {
105 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally
106 }
107 get_contested_resource_vote_state_request_v0::ResultType::VoteTally => {
108 ContestedDocumentVotePollDriveQueryResultType::VoteTally
109 }
110 },
111 start_at: v
112 .start_at_identifier_info
113 .map(|v| to_bytes32(&v.start_identifier).map(|id| (id, v.start_identifier_included)))
114 .transpose()
115 .map_err(|e| {
116 Error::RequestError {
117 error: format!(
118 "cannot decode start_at: {}",
119 e
120 )}}
121 )?,
122 offset: None, allow_include_locked_and_abstaining_vote_tally: v
124 .allow_include_locked_and_abstaining_vote_tally,
125 }
126 }
127 };
128 Ok(result)
129 }
130
131 fn try_to_request(&self) -> Result<GetContestedResourceVoteStateRequest, Error> {
132 use proto::get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0 as request_v0;
133 if self.offset.is_some() {
134 return Err(Error::RequestError{error:"ContestedDocumentVotePollDriveQuery.offset field is internal and must be set to None".into()});
135 }
136
137 let start_at_identifier_info = self.start_at.map(|v| request_v0::StartAtIdentifierInfo {
138 start_identifier: v.0.to_vec(),
139 start_identifier_included: v.1,
140 });
141
142 use proto::get_contested_resource_vote_state_request:: get_contested_resource_vote_state_request_v0::ResultType as GrpcResultType;
143 Ok(proto::get_contested_resource_vote_state_request::GetContestedResourceVoteStateRequestV0 {
144 prove:true,
145 contract_id:self.vote_poll.contract_id.to_vec(),
146 count: self.limit.map(|v| v as u32),
147 document_type_name: self.vote_poll.document_type_name.clone(),
148 index_name: self.vote_poll.index_name.clone(),
149 index_values: self.vote_poll.index_values.iter().map(|v|
150 dpp::bincode::encode_to_vec(v, BINCODE_CONFIG).map_err(|e|Error::RequestError { error: e.to_string() } )).collect::<Result<Vec<_>,_>>()?,
151 result_type:match self.result_type {
152 ContestedDocumentVotePollDriveQueryResultType::Documents => GrpcResultType::Documents.into(),
153 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => GrpcResultType::DocumentsAndVoteTally.into(),
154 ContestedDocumentVotePollDriveQueryResultType::VoteTally => GrpcResultType::VoteTally.into(),
155 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_) => return Err(Error::RequestError {
156 error: "can not perform a single document by contender query remotely".to_string(),
157 }),
158 },
159 start_at_identifier_info,
160 allow_include_locked_and_abstaining_vote_tally: self.allow_include_locked_and_abstaining_vote_tally,
161 }
162 .into())
163 }
164}
165
166fn to_bytes32(v: &[u8]) -> Result<[u8; 32], Error> {
167 let result: Result<[u8; 32], std::array::TryFromSliceError> = v.try_into();
168 match result {
169 Ok(id) => Ok(id),
170 Err(e) => Err(Error::RequestError {
171 error: format!("cannot decode id: {}", e),
172 }),
173 }
174}
175
176impl TryFromRequest<GetContestedResourceIdentityVotesRequest>
177 for ContestedResourceVotesGivenByIdentityQuery
178{
179 fn try_from_request(
180 grpc_request: GetContestedResourceIdentityVotesRequest,
181 ) -> Result<Self, Error> {
182 let proto::get_contested_resource_identity_votes_request::Version::V0(value) =
183 grpc_request.version.ok_or(Error::EmptyVersion)?;
184 let start_at = value
185 .start_at_vote_poll_id_info
186 .map(|v| {
187 to_bytes32(&v.start_at_poll_identifier)
188 .map(|id| (id, v.start_poll_identifier_included))
189 })
190 .transpose()?;
191
192 Ok(Self {
193 identity_id: Identifier::from_vec(value.identity_id.to_vec()).map_err(|e| {
194 Error::RequestError {
195 error: e.to_string(),
196 }
197 })?,
198 offset: None,
199 limit: value.limit.map(|x| x as u16),
200 start_at,
201 order_ascending: value.order_ascending,
202 })
203 }
204
205 fn try_to_request(&self) -> Result<GetContestedResourceIdentityVotesRequest, Error> {
206 use proto::get_contested_resource_identity_votes_request::get_contested_resource_identity_votes_request_v0 as request_v0;
207 if self.offset.is_some() {
208 return Err(Error::RequestError{error:"ContestedResourceVotesGivenByIdentityQuery.offset field is internal and must be set to None".into()});
209 }
210
211 Ok(proto::get_contested_resource_identity_votes_request::GetContestedResourceIdentityVotesRequestV0 {
212 prove: true,
213 identity_id: self.identity_id.to_vec(),
214 offset: self.offset.map(|x| x as u32),
215 limit: self.limit.map(|x| x as u32),
216 start_at_vote_poll_id_info: self.start_at.map(|(id, included)| {
217 request_v0::StartAtVotePollIdInfo {
218 start_at_poll_identifier: id.to_vec(),
219 start_poll_identifier_included: included,
220 }
221 }),
222 order_ascending: self.order_ascending,
223 }.into()
224 )
225 }
226}
227
228use dapi_grpc::platform::v0::get_contested_resource_voters_for_identity_request;
229
230impl TryFromRequest<GetContestedResourceVotersForIdentityRequest>
231 for ContestedDocumentVotePollVotesDriveQuery
232{
233 fn try_from_request(
234 value: GetContestedResourceVotersForIdentityRequest,
235 ) -> Result<Self, Error> {
236 let result = match value.version.ok_or(Error::EmptyVersion)? {
237 get_contested_resource_voters_for_identity_request::Version::V0(v) => {
238 ContestedDocumentVotePollVotesDriveQuery {
239 vote_poll: ContestedDocumentResourceVotePoll {
240 contract_id: Identifier::from_bytes(&v.contract_id).map_err(|e| {
241 Error::RequestError {
242 error: format!("cannot decode contract id: {}", e),
243 }
244 })?,
245 document_type_name: v.document_type_name.clone(),
246 index_name: v.index_name.clone(),
247 index_values: bincode_decode_values(v.index_values.iter())?,
248 },
249 contestant_id: Identifier::from_bytes(&v.contestant_id).map_err(|e| {
250 Error::RequestError {
251 error: format!("cannot decode contestant_id: {}", e),
252 }
253 })?,
254 limit: v.count.map(|v| v as u16),
255 offset: None,
256 start_at: v
257 .start_at_identifier_info
258 .map(|v| {
259 to_bytes32(&v.start_identifier)
260 .map(|id| (id, v.start_identifier_included))
261 })
262 .transpose()
263 .map_err(|e| Error::RequestError {
264 error: format!("cannot decode start_at value: {}", e),
265 })?,
266 order_ascending: v.order_ascending,
267 }
268 }
269 };
270
271 Ok(result)
272 }
273 fn try_to_request(&self) -> Result<GetContestedResourceVotersForIdentityRequest, Error> {
274 use proto::get_contested_resource_voters_for_identity_request::get_contested_resource_voters_for_identity_request_v0 as request_v0;
275 if self.offset.is_some() {
276 return Err(Error::RequestError{error:"ContestedDocumentVotePollVotesDriveQuery.offset field is internal and must be set to None".into()});
277 }
278
279 Ok(proto::get_contested_resource_voters_for_identity_request::GetContestedResourceVotersForIdentityRequestV0 {
280 prove:true,
281 contract_id: self.vote_poll.contract_id.to_vec(),
282 document_type_name: self.vote_poll.document_type_name.clone(),
283 index_name: self.vote_poll.index_name.clone(),
284 index_values: self.vote_poll.index_values.iter().map(|v|
285 dpp::bincode::encode_to_vec(v, BINCODE_CONFIG).map_err(|e|
286 Error::RequestError { error: e.to_string()})).collect::<Result<Vec<_>,_>>()?,
287 order_ascending: self.order_ascending,
288 count: self.limit.map(|v| v as u32),
289 contestant_id: self.contestant_id.to_vec(),
290 start_at_identifier_info: self.start_at.map(|v| request_v0::StartAtIdentifierInfo{
291 start_identifier: v.0.to_vec(),
292 start_identifier_included: v.1,
293 }),
294 }
295 .into())
296 }
297}
298
299impl TryFromRequest<GetContestedResourcesRequest> for VotePollsByDocumentTypeQuery {
300 fn try_from_request(value: GetContestedResourcesRequest) -> Result<Self, Error> {
301 let result = match value.version.ok_or(Error::EmptyVersion)? {
302 get_contested_resources_request::Version::V0(req) => VotePollsByDocumentTypeQuery {
303 contract_id: Identifier::from_bytes(&req.contract_id).map_err(|e| {
304 Error::RequestError {
305 error: format!("cannot decode contract id: {}", e),
306 }
307 })?,
308 document_type_name: req.document_type_name.clone(),
309 index_name: req.index_name.clone(),
310 start_at_value: req
311 .start_at_value_info
312 .map(|i| {
313 let (value, _): (Value, _) =
314 bincode::decode_from_slice(&i.start_value, BINCODE_CONFIG).map_err(
315 |e| Error::RequestError {
316 error: format!("cannot decode start value: {}", e),
317 },
318 )?;
319 Ok::<_, Error>((value, i.start_value_included))
320 })
321 .transpose()?,
322 start_index_values: bincode_decode_values(req.start_index_values.iter())?,
323 end_index_values: bincode_decode_values(req.end_index_values.iter())?,
324 limit: req.count.map(|v| v as u16),
325 order_ascending: req.order_ascending,
326 },
327 };
328 Ok(result)
329 }
330
331 fn try_to_request(&self) -> Result<GetContestedResourcesRequest, Error> {
332 Ok(GetContestedResourcesRequestV0 {
333 prove: true,
334 contract_id: self.contract_id.to_vec(),
335 count: self.limit.map(|v| v as u32),
336 document_type_name: self.document_type_name.clone(),
337 end_index_values: bincode_encode_values(&self.end_index_values)?,
338 start_index_values: bincode_encode_values(&self.start_index_values)?,
339 index_name: self.index_name.clone(),
340 order_ascending: self.order_ascending,
341 start_at_value_info: self
342 .start_at_value
343 .as_ref()
344 .map(|(start_value, start_value_included)| {
345 Ok::<_, Error>(get_contested_resources_request_v0::StartAtValueInfo {
346 start_value: bincode::encode_to_vec(start_value, BINCODE_CONFIG).map_err(
347 |e| Error::RequestError {
348 error: format!("cannot encode start value: {}", e),
349 },
350 )?,
351 start_value_included: *start_value_included,
352 })
353 })
354 .transpose()?,
355 }
356 .into())
357 }
358}
359
360impl TryFromRequest<GetVotePollsByEndDateRequest> for VotePollsByEndDateDriveQuery {
361 fn try_from_request(value: GetVotePollsByEndDateRequest) -> Result<Self, Error> {
362 let result = match value.version.ok_or(Error::EmptyVersion)? {
363 get_vote_polls_by_end_date_request::Version::V0(v) => VotePollsByEndDateDriveQuery {
364 start_time: v
365 .start_time_info
366 .map(|v| (v.start_time_ms, v.start_time_included)),
367 end_time: v
368 .end_time_info
369 .map(|v| (v.end_time_ms, v.end_time_included)),
370 limit: v.limit.map(|v| v as u16),
371 offset: v.offset.map(|v| v as u16),
372 order_ascending: v.ascending,
373 },
374 };
375 Ok(result)
376 }
377
378 fn try_to_request(&self) -> Result<GetVotePollsByEndDateRequest, Error> {
379 use proto::get_vote_polls_by_end_date_request::get_vote_polls_by_end_date_request_v0 as request_v0;
380 if self.offset.is_some() {
381 return Err(Error::RequestError {
382 error:
383 "VotePollsByEndDateDriveQuery.offset field is internal and must be set to None"
384 .into(),
385 });
386 }
387
388 Ok(
389 proto::get_vote_polls_by_end_date_request::GetVotePollsByEndDateRequestV0 {
390 prove: true,
391 start_time_info: self.start_time.map(|(start_time_ms, start_time_included)| {
392 request_v0::StartAtTimeInfo {
393 start_time_ms,
394 start_time_included,
395 }
396 }),
397 end_time_info: self.end_time.map(|(end_time_ms, end_time_included)| {
398 request_v0::EndAtTimeInfo {
399 end_time_ms,
400 end_time_included,
401 }
402 }),
403 limit: self.limit.map(|v| v as u32),
404 offset: self.offset.map(|v| v as u32),
405 ascending: self.order_ascending,
406 }
407 .into(),
408 )
409 }
410}
411
412impl TryFromRequest<GetPrefundedSpecializedBalanceRequest> for Identifier {
413 fn try_to_request(&self) -> Result<GetPrefundedSpecializedBalanceRequest, Error> {
414 Ok(
415 proto::get_prefunded_specialized_balance_request::GetPrefundedSpecializedBalanceRequestV0 {
416 prove:true,
417 id: self.to_vec(),
418 }.into()
419 )
420 }
421
422 fn try_from_request(
423 grpc_request: GetPrefundedSpecializedBalanceRequest,
424 ) -> Result<Self, Error> {
425 match grpc_request.version.ok_or(Error::EmptyVersion)? {
426 proto::get_prefunded_specialized_balance_request::Version::V0(v) => {
427 Identifier::from_bytes(&v.id).map_err(|e| Error::RequestError {
428 error: format!("cannot decode id: {}", e),
429 })
430 }
431 }
432 }
433}
434
435fn bincode_decode_values<V: AsRef<[u8]>, T: IntoIterator<Item = V>>(
439 values: T,
440) -> Result<Vec<Value>, Error> {
441 values
442 .into_iter()
443 .map(|v| {
444 dpp::bincode::decode_from_slice(v.as_ref(), BINCODE_CONFIG)
445 .map_err(|e| Error::RequestError {
446 error: format!("cannot decode value: {}", e),
447 })
448 .map(|(v, _)| v)
449 })
450 .collect()
451}
452
453fn bincode_encode_values<'a, T: IntoIterator<Item = &'a Value>>(
457 values: T,
458) -> Result<Vec<Vec<u8>>, Error> {
459 values
460 .into_iter()
461 .map(|v| {
462 dpp::bincode::encode_to_vec(v, BINCODE_CONFIG).map_err(|e| Error::RequestError {
463 error: format!("cannot encode value: {}", e),
464 })
465 })
466 .collect::<Result<Vec<_>, _>>()
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use dpp::identifier::Identifier;
473 use dpp::platform_value::Value;
474
475 #[test]
480 fn test_to_bytes32_valid() {
481 let input = [0xABu8; 32];
482 let result = to_bytes32(&input).expect("should convert 32-byte slice");
483 assert_eq!(result, input);
484 }
485
486 #[test]
487 fn test_to_bytes32_invalid_length() {
488 let short = [0u8; 16];
490 assert!(to_bytes32(&short).is_err());
491
492 let long = [0u8; 33];
494 assert!(to_bytes32(&long).is_err());
495
496 assert!(to_bytes32(&[]).is_err());
498 }
499
500 #[test]
505 fn test_bincode_encode_decode_roundtrip() {
506 let values = vec![
507 Value::Text("hello".to_string()),
508 Value::U64(42),
509 Value::Bool(true),
510 ];
511 let encoded = bincode_encode_values(&values).expect("encoding should succeed");
512 assert_eq!(encoded.len(), 3);
513
514 let decoded = bincode_decode_values(encoded.iter()).expect("decoding should succeed");
515 assert_eq!(decoded, values);
516 }
517
518 #[test]
519 fn test_bincode_decode_empty() {
520 let empty: Vec<Vec<u8>> = vec![];
521 let result = bincode_decode_values(empty.iter()).expect("empty input should succeed");
522 assert!(result.is_empty());
523 }
524
525 #[test]
526 fn test_bincode_decode_invalid() {
527 let garbage = [vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB]];
528 let result = bincode_decode_values(garbage.iter());
529 assert!(
530 result.is_err(),
531 "invalid bincode bytes should produce an error"
532 );
533 }
534
535 #[test]
540 fn test_contested_document_vote_poll_result_type_roundtrip() {
541 use get_contested_resource_vote_state_request_v0::ResultType as GrpcResultType;
542
543 let cases = vec![
544 (
545 GrpcResultType::Documents,
546 ContestedDocumentVotePollDriveQueryResultType::Documents,
547 ),
548 (
549 GrpcResultType::VoteTally,
550 ContestedDocumentVotePollDriveQueryResultType::VoteTally,
551 ),
552 (
553 GrpcResultType::DocumentsAndVoteTally,
554 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally,
555 ),
556 ];
557
558 for (grpc_val, expected_drive) in cases {
559 let drive_val =
561 ContestedDocumentVotePollDriveQueryResultType::try_from_request(grpc_val)
562 .expect("try_from_request should succeed");
563 assert_eq!(drive_val, expected_drive);
564
565 let back = drive_val
567 .try_to_request()
568 .expect("try_to_request should succeed");
569 assert_eq!(back, grpc_val);
570 }
571 }
572
573 #[test]
578 fn test_contested_document_vote_poll_query_roundtrip() {
579 let contract_id = Identifier::from_bytes(&[1u8; 32]).unwrap();
580 let index_values = vec![Value::Text("dash".to_string())];
581
582 let query = ContestedDocumentVotePollDriveQuery {
583 vote_poll: ContestedDocumentResourceVotePoll {
584 contract_id,
585 document_type_name: "domain".to_string(),
586 index_name: "parentNameAndLabel".to_string(),
587 index_values: index_values.clone(),
588 },
589 result_type: ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally,
590 offset: None,
591 limit: Some(10),
592 start_at: None,
593 allow_include_locked_and_abstaining_vote_tally: true,
594 };
595
596 let grpc_request = query
597 .try_to_request()
598 .expect("try_to_request should succeed");
599
600 let roundtripped = ContestedDocumentVotePollDriveQuery::try_from_request(grpc_request)
601 .expect("try_from_request should succeed");
602
603 assert_eq!(
604 roundtripped.vote_poll.contract_id,
605 query.vote_poll.contract_id
606 );
607 assert_eq!(
608 roundtripped.vote_poll.document_type_name,
609 query.vote_poll.document_type_name
610 );
611 assert_eq!(
612 roundtripped.vote_poll.index_name,
613 query.vote_poll.index_name
614 );
615 assert_eq!(
616 roundtripped.vote_poll.index_values,
617 query.vote_poll.index_values
618 );
619 assert_eq!(roundtripped.result_type, query.result_type);
620 assert_eq!(roundtripped.limit, query.limit);
621 assert_eq!(roundtripped.start_at, query.start_at);
622 assert_eq!(
623 roundtripped.allow_include_locked_and_abstaining_vote_tally,
624 query.allow_include_locked_and_abstaining_vote_tally
625 );
626 }
627
628 #[test]
633 fn test_identifier_prefunded_balance_roundtrip() {
634 let id = Identifier::from_bytes(&[7u8; 32]).unwrap();
635
636 let grpc_request: GetPrefundedSpecializedBalanceRequest =
637 id.try_to_request().expect("try_to_request should succeed");
638
639 let roundtripped =
640 Identifier::try_from_request(grpc_request).expect("try_from_request should succeed");
641
642 assert_eq!(roundtripped, id);
643 }
644
645 #[test]
650 fn test_contested_result_type_rejects_single_document_by_contender() {
651 let contender_id = Identifier::from_bytes(&[0xCC; 32]).unwrap();
652 let result_type =
653 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(contender_id);
654
655 let result = result_type.try_to_request();
656 assert!(
657 result.is_err(),
658 "SingleDocumentByContender should not be convertible to a gRPC request"
659 );
660
661 let err_msg = format!("{}", result.unwrap_err());
662 assert!(
663 err_msg.contains("single document by contender"),
664 "error message should mention 'single document by contender', got: {}",
665 err_msg
666 );
667 }
668
669 #[test]
679 fn test_contested_document_vote_poll_query_rejects_offset() {
680 let contract_id = Identifier::from_bytes(&[2u8; 32]).unwrap();
681 let query = ContestedDocumentVotePollDriveQuery {
682 vote_poll: ContestedDocumentResourceVotePoll {
683 contract_id,
684 document_type_name: "d".to_string(),
685 index_name: "idx".to_string(),
686 index_values: vec![],
687 },
688 result_type: ContestedDocumentVotePollDriveQueryResultType::Documents,
689 offset: Some(5), limit: None,
691 start_at: None,
692 allow_include_locked_and_abstaining_vote_tally: false,
693 };
694
695 let err = query.try_to_request().unwrap_err();
696 let err_msg = format!("{}", err);
697 assert!(
698 err_msg.contains("offset"),
699 "error should mention offset, got: {err_msg}"
700 );
701 }
702
703 #[test]
709 fn test_contested_resource_votes_given_by_identity_rejects_offset() {
710 let id = Identifier::from_bytes(&[3u8; 32]).unwrap();
711 let query = ContestedResourceVotesGivenByIdentityQuery {
712 identity_id: id,
713 offset: Some(10), limit: None,
715 start_at: None,
716 order_ascending: true,
717 };
718 let err = query.try_to_request().unwrap_err();
719 let msg = format!("{}", err);
720 assert!(msg.contains("offset"), "error should mention offset: {msg}");
721 }
722
723 #[test]
724 fn test_contested_resource_votes_given_by_identity_from_request_bad_identity() {
725 use dapi_grpc::platform::v0::get_contested_resource_identity_votes_request::{
727 GetContestedResourceIdentityVotesRequestV0, Version as ReqVersion,
728 };
729 let request = GetContestedResourceIdentityVotesRequest {
730 version: Some(ReqVersion::V0(GetContestedResourceIdentityVotesRequestV0 {
731 identity_id: vec![0u8; 10],
732 start_at_vote_poll_id_info: None,
733 limit: None,
734 offset: None,
735 order_ascending: true,
736 prove: true,
737 })),
738 };
739 let err =
740 ContestedResourceVotesGivenByIdentityQuery::try_from_request(request).unwrap_err();
741 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
742 }
743
744 #[test]
745 fn test_contested_resource_votes_given_by_identity_from_request_bad_start_at() {
746 use dapi_grpc::platform::v0::get_contested_resource_identity_votes_request::{
748 get_contested_resource_identity_votes_request_v0::StartAtVotePollIdInfo,
749 GetContestedResourceIdentityVotesRequestV0, Version as ReqVersion,
750 };
751 let request = GetContestedResourceIdentityVotesRequest {
752 version: Some(ReqVersion::V0(GetContestedResourceIdentityVotesRequestV0 {
753 identity_id: vec![0u8; 32],
754 start_at_vote_poll_id_info: Some(StartAtVotePollIdInfo {
755 start_at_poll_identifier: vec![1u8; 9], start_poll_identifier_included: true,
757 }),
758 limit: None,
759 offset: None,
760 order_ascending: true,
761 prove: true,
762 })),
763 };
764 let err =
765 ContestedResourceVotesGivenByIdentityQuery::try_from_request(request).unwrap_err();
766 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
767 }
768
769 #[test]
770 fn test_contested_resource_votes_given_by_identity_missing_version() {
771 let request = GetContestedResourceIdentityVotesRequest { version: None };
772 let err =
773 ContestedResourceVotesGivenByIdentityQuery::try_from_request(request).unwrap_err();
774 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
775 }
776
777 #[test]
782 fn test_contested_document_vote_poll_votes_missing_version() {
783 let request = GetContestedResourceVotersForIdentityRequest { version: None };
784 let err = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request).unwrap_err();
785 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
786 }
787
788 #[test]
789 fn test_contested_document_vote_poll_votes_from_request_bad_contract_id() {
790 use dapi_grpc::platform::v0::get_contested_resource_voters_for_identity_request::{
791 GetContestedResourceVotersForIdentityRequestV0, Version as ReqVersion,
792 };
793 let request = GetContestedResourceVotersForIdentityRequest {
794 version: Some(ReqVersion::V0(
795 GetContestedResourceVotersForIdentityRequestV0 {
796 contract_id: vec![0u8; 7], document_type_name: "d".to_string(),
798 index_name: "i".to_string(),
799 index_values: vec![],
800 contestant_id: vec![0u8; 32],
801 start_at_identifier_info: None,
802 order_ascending: true,
803 count: None,
804 prove: true,
805 },
806 )),
807 };
808 let err = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request).unwrap_err();
809 match err {
810 Error::RequestError { error } => assert!(error.contains("contract id"), "got: {error}"),
811 other => panic!("expected RequestError, got: {other:?}"),
812 }
813 }
814
815 #[test]
816 fn test_contested_document_vote_poll_votes_from_request_bad_contestant_id() {
817 use dapi_grpc::platform::v0::get_contested_resource_voters_for_identity_request::{
818 GetContestedResourceVotersForIdentityRequestV0, Version as ReqVersion,
819 };
820 let request = GetContestedResourceVotersForIdentityRequest {
821 version: Some(ReqVersion::V0(
822 GetContestedResourceVotersForIdentityRequestV0 {
823 contract_id: vec![0u8; 32],
824 document_type_name: "d".to_string(),
825 index_name: "i".to_string(),
826 index_values: vec![],
827 contestant_id: vec![0u8; 5], start_at_identifier_info: None,
829 order_ascending: true,
830 count: None,
831 prove: true,
832 },
833 )),
834 };
835 let err = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request).unwrap_err();
836 match err {
837 Error::RequestError { error } => {
838 assert!(error.contains("contestant_id"), "got: {error}")
839 }
840 other => panic!("expected RequestError, got: {other:?}"),
841 }
842 }
843
844 #[test]
845 fn test_contested_document_vote_poll_votes_rejects_offset() {
846 let contract_id = Identifier::from_bytes(&[0u8; 32]).unwrap();
847 let contestant_id = Identifier::from_bytes(&[1u8; 32]).unwrap();
848 let q = ContestedDocumentVotePollVotesDriveQuery {
849 vote_poll: ContestedDocumentResourceVotePoll {
850 contract_id,
851 document_type_name: "d".to_string(),
852 index_name: "i".to_string(),
853 index_values: vec![],
854 },
855 contestant_id,
856 limit: None,
857 offset: Some(7),
858 start_at: None,
859 order_ascending: true,
860 };
861 let err = q.try_to_request().unwrap_err();
862 assert!(format!("{err}").contains("offset"));
863 }
864
865 #[test]
870 fn test_vote_polls_by_document_type_missing_version() {
871 let request = GetContestedResourcesRequest { version: None };
872 let err = VotePollsByDocumentTypeQuery::try_from_request(request).unwrap_err();
873 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
874 }
875
876 #[test]
877 fn test_vote_polls_by_document_type_from_request_bad_contract_id() {
878 let request = GetContestedResourcesRequest {
879 version: Some(get_contested_resources_request::Version::V0(
880 GetContestedResourcesRequestV0 {
881 contract_id: vec![0u8; 6],
882 document_type_name: "d".to_string(),
883 index_name: "i".to_string(),
884 start_at_value_info: None,
885 start_index_values: vec![],
886 end_index_values: vec![],
887 count: None,
888 order_ascending: true,
889 prove: true,
890 },
891 )),
892 };
893 let err = VotePollsByDocumentTypeQuery::try_from_request(request).unwrap_err();
894 match err {
895 Error::RequestError { error } => assert!(error.contains("contract id"), "got: {error}"),
896 other => panic!("expected RequestError, got: {other:?}"),
897 }
898 }
899
900 #[test]
901 fn test_vote_polls_by_document_type_from_request_bad_start_value() {
902 let request = GetContestedResourcesRequest {
903 version: Some(get_contested_resources_request::Version::V0(
904 GetContestedResourcesRequestV0 {
905 contract_id: vec![0u8; 32],
906 document_type_name: "d".to_string(),
907 index_name: "i".to_string(),
908 start_at_value_info: Some(
909 get_contested_resources_request_v0::StartAtValueInfo {
910 start_value: vec![0xFFu8, 0xFE, 0xFD], start_value_included: true,
912 },
913 ),
914 start_index_values: vec![],
915 end_index_values: vec![],
916 count: None,
917 order_ascending: true,
918 prove: true,
919 },
920 )),
921 };
922 let err = VotePollsByDocumentTypeQuery::try_from_request(request).unwrap_err();
923 match err {
924 Error::RequestError { error } => {
925 assert!(error.contains("decode start value"), "got: {error}")
926 }
927 other => panic!("expected RequestError, got: {other:?}"),
928 }
929 }
930
931 #[test]
932 fn test_vote_polls_by_document_type_roundtrip_with_start_at_value() {
933 let contract_id = Identifier::from_bytes(&[9u8; 32]).unwrap();
934 let query = VotePollsByDocumentTypeQuery {
935 contract_id,
936 document_type_name: "domain".to_string(),
937 index_name: "parent".to_string(),
938 start_at_value: Some((Value::Text("dash".to_string()), true)),
939 start_index_values: vec![Value::Text("a".to_string())],
940 end_index_values: vec![Value::Text("z".to_string())],
941 limit: Some(20),
942 order_ascending: false,
943 };
944
945 let grpc = query.try_to_request().expect("try_to_request succeeds");
946 let back = VotePollsByDocumentTypeQuery::try_from_request(grpc)
947 .expect("try_from_request succeeds");
948
949 assert_eq!(back.contract_id, query.contract_id);
950 assert_eq!(back.document_type_name, query.document_type_name);
951 assert_eq!(back.index_name, query.index_name);
952 assert_eq!(back.start_at_value, query.start_at_value);
953 assert_eq!(back.start_index_values, query.start_index_values);
954 assert_eq!(back.end_index_values, query.end_index_values);
955 assert_eq!(back.limit, query.limit);
956 assert_eq!(back.order_ascending, query.order_ascending);
957 }
958
959 #[test]
964 fn test_vote_polls_by_end_date_roundtrip() {
965 let q = VotePollsByEndDateDriveQuery {
966 start_time: Some((1, false)),
967 end_time: Some((10_000, true)),
968 limit: Some(10),
969 offset: None,
970 order_ascending: false,
971 };
972 let grpc = q.try_to_request().expect("try_to_request ok");
973 let back =
974 VotePollsByEndDateDriveQuery::try_from_request(grpc).expect("try_from_request ok");
975 assert_eq!(back.start_time, q.start_time);
976 assert_eq!(back.end_time, q.end_time);
977 assert_eq!(back.limit, q.limit);
978 assert_eq!(back.order_ascending, q.order_ascending);
979 }
980
981 #[test]
982 fn test_vote_polls_by_end_date_missing_version() {
983 let request = GetVotePollsByEndDateRequest { version: None };
984 let err = VotePollsByEndDateDriveQuery::try_from_request(request).unwrap_err();
985 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
986 }
987
988 #[test]
993 fn test_identifier_prefunded_balance_missing_version() {
994 let request = GetPrefundedSpecializedBalanceRequest { version: None };
995 let err = Identifier::try_from_request(request).unwrap_err();
996 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
997 }
998
999 #[test]
1000 fn test_identifier_prefunded_balance_bad_id_length() {
1001 let request = GetPrefundedSpecializedBalanceRequest {
1002 version: Some(
1003 proto::get_prefunded_specialized_balance_request::Version::V0(
1004 proto::get_prefunded_specialized_balance_request::GetPrefundedSpecializedBalanceRequestV0 {
1005 id: vec![0u8; 10], prove: true,
1007 },
1008 ),
1009 ),
1010 };
1011 let err = Identifier::try_from_request(request).unwrap_err();
1012 match err {
1013 Error::RequestError { error } => assert!(error.contains("decode id"), "got: {error}"),
1014 other => panic!("expected RequestError, got: {other:?}"),
1015 }
1016 }
1017
1018 #[test]
1023 fn test_contested_document_vote_poll_query_missing_version() {
1024 let request = GetContestedResourceVoteStateRequest { version: None };
1025 let err = ContestedDocumentVotePollDriveQuery::try_from_request(request).unwrap_err();
1026 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
1027 }
1028
1029 #[test]
1030 fn test_contested_document_vote_poll_query_from_request_bad_contract_id() {
1031 let request = GetContestedResourceVoteStateRequest {
1032 version: Some(get_contested_resource_vote_state_request::Version::V0(
1033 proto::get_contested_resource_vote_state_request::GetContestedResourceVoteStateRequestV0 {
1034 contract_id: vec![0u8; 9], document_type_name: "d".to_string(),
1036 index_name: "i".to_string(),
1037 index_values: vec![],
1038 result_type: 0,
1039 start_at_identifier_info: None,
1040 allow_include_locked_and_abstaining_vote_tally: true,
1041 count: None,
1042 prove: true,
1043 },
1044 )),
1045 };
1046 let err = ContestedDocumentVotePollDriveQuery::try_from_request(request).unwrap_err();
1047 match err {
1048 Error::RequestError { error } => assert!(error.contains("contract id"), "got: {error}"),
1049 other => panic!("expected RequestError, got: {other:?}"),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_contested_document_vote_poll_query_from_request_bad_start_at_identifier() {
1055 let request = GetContestedResourceVoteStateRequest {
1056 version: Some(get_contested_resource_vote_state_request::Version::V0(
1057 proto::get_contested_resource_vote_state_request::GetContestedResourceVoteStateRequestV0 {
1058 contract_id: vec![0u8; 32],
1059 document_type_name: "d".to_string(),
1060 index_name: "i".to_string(),
1061 index_values: vec![],
1062 result_type: 0,
1063 start_at_identifier_info: Some(
1064 get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo {
1065 start_identifier: vec![0u8; 10], start_identifier_included: true,
1067 },
1068 ),
1069 allow_include_locked_and_abstaining_vote_tally: true,
1070 count: None,
1071 prove: true,
1072 },
1073 )),
1074 };
1075 let err = ContestedDocumentVotePollDriveQuery::try_from_request(request).unwrap_err();
1076 match err {
1077 Error::RequestError { error } => assert!(error.contains("start_at"), "got: {error}"),
1078 other => panic!("expected RequestError, got: {other:?}"),
1079 }
1080 }
1081
1082 #[test]
1087 fn test_bincode_decode_mixed_valid_and_invalid() {
1088 let mut encoded_valid = bincode_encode_values(&[Value::Text("x".to_string())]).unwrap();
1089 encoded_valid.push(vec![0xFF, 0xFE, 0xFD]);
1091 let result = bincode_decode_values(encoded_valid.iter());
1092 assert!(result.is_err(), "mixed input must fail");
1093 }
1094
1095 #[test]
1100 fn test_vote_polls_by_end_date_rejects_offset() {
1101 let query = VotePollsByEndDateDriveQuery {
1102 start_time: Some((1000, true)),
1103 end_time: Some((2000, false)),
1104 limit: Some(5),
1105 offset: Some(10), order_ascending: true,
1107 };
1108
1109 let result = query.try_to_request();
1110 assert!(
1111 result.is_err(),
1112 "offset must be None for try_to_request to succeed"
1113 );
1114
1115 let err_msg = format!("{}", result.unwrap_err());
1116 assert!(
1117 err_msg.contains("offset"),
1118 "error message should mention 'offset', got: {}",
1119 err_msg
1120 );
1121 }
1122}