Skip to main content

cli_example/
cli_example.rs

1//! ## Available Commands
2//!
3//! - `list_bills`        : List recent bills introduced in Congress.
4//! - `current_congress`  : Display information about the current congress session.
5//! - `list_nominations`  : List recent nominations.
6//! - `list_treaties`     : List recent treaties.
7//! - `member_details`    : Get detailed information about a specific member (requires additional argument: `bioguide_id`).
8//! - `bill_details`      : Get detailed information about a specific bill (requires additional arguments: `congress`, `bill_type`, `bill_number`).
9//! - `current_members`   : Fetch and display all current members of Congress.
10//! - `list_committees`   : List all congressional committees.
11//! - `list_laws`         : List recently passed laws.
12//! - `list_amendments`   : List recent amendments.
13//!
14//! ## Usage
15//!
16//! ```bash
17//! cargo run -- <command> [additional arguments]
18//!
19//! # Examples:
20//! cargo run -- list_bills {amount}
21//! cargo run -- current_congress
22//! cargo run -- member_details {bioguide_id}
23//! cargo run -- bill_details {congress} {bill_type} {bill_number}
24//! cargo run -- current_members
25//! cargo run -- list_committees
26//! cargo run -- list_laws
27//! cargo run -- list_amendments
28//! ```
29
30use std::env;
31use std::error::Error;
32use std::process;
33
34use cdg_api::cdg_types::*;
35use cdg_api::endpoints::Endpoints;
36use cdg_api::param_models::{
37    AmendmentListParams, BillActionsParams, BillDetailsParams, BillListParams, CommitteeListParams,
38    LawParams, MemberDetailsParams, MemberListParams, NominationListParams, TreatyListParams,
39};
40use cdg_api::response_models::{
41    AmendmentsResponse, BillActionsResponse, BillDetailsResponse, BillsResponse,
42    CommitteesResponse, CongressDetailsResponse, LawsResponse, MemberDetailsResponse,
43    MembersResponse, NominationsResponse, PrimaryResponse, TreatiesResponse,
44};
45use cdg_api::CongressApiClient;
46
47fn main() {
48    if let Err(e) = run() {
49        eprintln!("Error: {}", e);
50        process::exit(1);
51    }
52}
53
54/// Runs the main application logic.
55/// Some of these can take a considerable amount of time to fetch all data depending on the amount requested.
56fn run() -> Result<(), Box<dyn Error>> {
57    // Retrieve the API key from the environment variable or use default
58    let api_key = env::var("CDG_API_KEY").ok();
59    let client = CongressApiClient::new(api_key)?;
60
61    // Collect command-line arguments
62    let args: Vec<String> = env::args().collect();
63
64    if args.len() < 2 {
65        print_usage();
66        return Err("No command provided.".into());
67    }
68
69    // The first argument is the command
70    let command = args[1].to_lowercase();
71    let results_max = 1000;
72
73    match command.as_str() {
74        "list_bills" => {
75            if args.len() < 3 {
76                eprintln!("Usage: cargo run -- list_bills {{amount}}");
77                return Err("Missing amount for list_bills command.".into());
78            }
79            let bill_amount = args[2].parse::<u32>().unwrap_or(10);
80            println!("Searching for {} bills...", bill_amount);
81            let limit = 250;
82            let all_bills = fetch_all(
83                &client,
84                |offset, limit| {
85                    Endpoints::BillList(
86                        BillListParams::default()
87                            .format(FormatType::Json)
88                            .limit(limit as u32)
89                            .offset(offset as u32),
90                    )
91                },
92                |response: &BillsResponse| response.bills.clone(),
93                bill_amount as usize,
94                limit,
95            )?;
96            display_bills(&all_bills);
97        }
98        "current_congress" => {
99            let endpoint =
100                Endpoints::CongressCurrent(cdg_api::param_models::CongressCurrentParams::default());
101            let response: CongressDetailsResponse = client.fetch(endpoint)?;
102            display_congress_details(&response);
103        }
104        "list_nominations" => {
105            let limit = 250;
106            let all_nominations: Vec<cdg_api::response_models::NominationItem> = fetch_all(
107                &client,
108                |offset, limit| {
109                    Endpoints::NominationList(
110                        NominationListParams::default()
111                            .format(FormatType::Json)
112                            .limit(limit as u32)
113                            .offset(offset as u32),
114                    )
115                },
116                |response: &NominationsResponse| response.nominations.clone(),
117                results_max,
118                limit,
119            )
120            .unwrap_or_default();
121            display_nominations(&NominationsResponse {
122                nominations: all_nominations,
123                unknown: None,
124            });
125        }
126        "list_treaties" => {
127            let limit = 250;
128            let all_treaties = fetch_all(
129                &client,
130                |offset, limit| {
131                    Endpoints::TreatyList(
132                        TreatyListParams::default()
133                            .format(FormatType::Json)
134                            .limit(limit as u32)
135                            .offset(offset as u32),
136                    )
137                },
138                |response: &TreatiesResponse| response.treaty.clone(),
139                results_max,
140                limit,
141            )?;
142            display_treaties(&TreatiesResponse {
143                treaty: all_treaties,
144                unknown: None,
145            });
146        }
147        "member_details" => {
148            if args.len() < 3 {
149                eprintln!("Usage: cargo run -- member_details <bioguide_id>");
150                return Err("Missing bioguide_id for member_details command.".into());
151            }
152            let bioguide_id = &args[2];
153            let params = MemberDetailsParams::default();
154            let endpoint = Endpoints::MemberDetails(bioguide_id.clone(), params);
155            let response: MemberDetailsResponse = client.fetch(endpoint)?;
156            display_member_details(&response);
157        }
158        "bill_details" => {
159            if args.len() < 5 {
160                eprintln!("Usage: cargo run -- bill_details <congress> <bill_type> <bill_number>");
161                return Err("Missing arguments for bill_details command.".into());
162            }
163            let congress: u32 = args[2].parse()?;
164            let bill_type = BillType::from_str(&args[3]).unwrap_or_default();
165            let bill_number: u32 = args[4].parse()?;
166            let params = BillDetailsParams::default();
167            let endpoint = Endpoints::BillDetails(congress, bill_type, bill_number, params);
168            let response: BillDetailsResponse = client.fetch(endpoint)?;
169            display_bill_details(&response);
170        }
171        "bill_actions" => {
172            if args.len() < 5 {
173                eprintln!("Usage: cargo run -- bill_actions <congress> <bill_type> <bill_number>");
174                return Err("Missing arguments for bill_actions command.".into());
175            }
176            let congress: u32 = args[2].parse()?;
177            let bill_type = BillType::from_str(&args[3]).unwrap_or_default();
178            let bill_number: u32 = args[4].parse()?;
179            let params = BillActionsParams::default();
180            let endpoint = Endpoints::BillActions(congress, bill_type, bill_number, params);
181            let response: BillActionsResponse = client.fetch(endpoint)?;
182            display_billacts_details(&response);
183        }
184        "current_members" => {
185            let limit = 250;
186            let all_members = fetch_all(
187                &client,
188                |offset, limit| {
189                    Endpoints::MemberList(
190                        MemberListParams::default()
191                            .format(FormatType::Json)
192                            .limit(limit as u32)
193                            .offset(offset as u32)
194                            .current_member(true),
195                    )
196                },
197                |response: &MembersResponse| response.members.clone(),
198                results_max,
199                limit,
200            )?;
201            display_members(&MembersResponse {
202                members: all_members,
203                unknown: None,
204            });
205        }
206        "list_committees" => {
207            let limit = 250;
208            let all_committees = fetch_all(
209                &client,
210                |offset, limit| {
211                    Endpoints::CommitteeList(
212                        CommitteeListParams::default()
213                            .format(FormatType::Json)
214                            .limit(limit as u32)
215                            .offset(offset as u32),
216                    )
217                },
218                |response: &CommitteesResponse| response.committees.clone(),
219                results_max,
220                limit,
221            )?;
222            display_committees(&CommitteesResponse {
223                committees: all_committees,
224            });
225        }
226        "list_laws" => {
227            let limit = 250;
228            let congress = 118; // Current Congress
229            let all_laws = fetch_all(
230                &client,
231                |offset, limit| {
232                    Endpoints::LawByCongress(
233                        congress,
234                        LawParams::default()
235                            .format(FormatType::Json)
236                            .limit(limit as u32)
237                            .offset(offset as u32),
238                    )
239                },
240                |response: &LawsResponse| response.bills.clone(),
241                results_max,
242                limit,
243            )?;
244            display_laws(&LawsResponse {
245                bills: all_laws,
246                unknown: None,
247            });
248        }
249        "list_amendments" => {
250            let limit = 250;
251            let all_amendments = fetch_all(
252                &client,
253                |offset, limit| {
254                    Endpoints::AmendmentList(
255                        AmendmentListParams::default()
256                            .format(FormatType::Json)
257                            .limit(limit as u32)
258                            .offset(offset as u32),
259                    )
260                },
261                |response: &AmendmentsResponse| response.amendments.clone(),
262                results_max,
263                limit,
264            )?;
265            display_amendments(&AmendmentsResponse {
266                amendments: all_amendments,
267                unknown: None,
268            });
269        }
270        _ => {
271            println!("Unknown command: {}", command);
272            print_usage();
273            return Err("Invalid command.".into());
274        }
275    }
276
277    Ok(())
278}
279
280/// Fetches all items from a paginated endpoint.
281///
282/// # Arguments
283///
284/// * `client` - Reference to the CongressApiClient.
285/// * `endpoint_fn` - A closure that takes `offset` and `limit` and returns an `Endpoints` enum.
286/// * `extract_fn` - A closure that takes a reference to the response and returns a vector of items.
287/// * `max` - The maximum number of items to fetch.
288/// * `page_limit` - The number of items to fetch per request.
289///
290/// # Returns
291///
292/// A `Result` containing a vector of items or an error.
293fn fetch_all<T, U, F, G>(
294    client: &CongressApiClient,
295    endpoint_fn: F,
296    extract_fn: G,
297    max: usize,
298    page_limit: usize,
299) -> Result<Vec<U>, Box<dyn Error>>
300where
301    F: Fn(usize, usize) -> Endpoints,
302    G: Fn(&T) -> Vec<U>,
303    T: serde::de::DeserializeOwned + PrimaryResponse,
304{
305    let mut all_items = Vec::new();
306    let mut offset = 0;
307
308    loop {
309        let endpoint = endpoint_fn(offset, page_limit);
310        let response: T = client.fetch(endpoint.clone())?;
311        let items = extract_fn(&response);
312        let fetched_count = items.len();
313        all_items.extend(items);
314
315        if all_items.len() >= max {
316            all_items.truncate(max);
317            break;
318        }
319
320        if fetched_count < page_limit || all_items.len() >= max {
321            break;
322        }
323
324        offset += fetched_count;
325    }
326
327    Ok(all_items)
328}
329
330/// Prints the usage instructions.
331fn print_usage() {
332    println!("Usage: cargo run -- <command> [additional arguments]");
333    println!("\nAvailable commands:");
334    println!("  list_bills {{amount}}           : List recent bills introduced in Congress.");
335    println!("  current_congress                : Display information about the current congress session.");
336    println!("  list_nominations                : List recent nominations.");
337    println!("  list_treaties                   : List recent treaties.");
338    println!(
339        "  member_details {{bioguide_id}}  : Get detailed information about a specific member."
340    );
341    println!("  bill_details                    : Get detailed information about a specific bill.");
342    println!("    {{congress}}");
343    println!("     {{bill_type}}");
344    println!("      {{bill_number}}");
345    println!(
346        "  current_members                 : Fetch and display all current members of Congress."
347    );
348    println!("  list_committees                 : List all congressional committees.");
349    println!("  list_laws                       : List recently passed laws.");
350    println!("  list_amendments                 : List recent amendments.");
351}
352
353/// Displays the list of members in a formatted manner.
354fn display_members(response: &MembersResponse) {
355    println!("Current Members of Congress:");
356    for member in &response.members {
357        println!("----------------------------------------");
358        println!(
359            "Name       : {}",
360            member.name.clone().unwrap_or_else(|| "N/A".to_string())
361        );
362        println!(
363            "bioguideId : {}",
364            member
365                .bioguide_id
366                .clone()
367                .unwrap_or_else(|| "N/A".to_string())
368        );
369        println!(
370            "State      : {}",
371            member.state.clone().unwrap_or_else(|| "N/A".to_string())
372        );
373        println!(
374            "Party      : {}",
375            member
376                .party_name
377                .clone()
378                .unwrap_or_else(|| "N/A".to_string())
379        );
380        println!("District   : {}", member.district.unwrap_or(0));
381        let depiction = member.depiction.clone().unwrap_or_default();
382        println!(
383            "Image URL  : {}",
384            depiction.image_url.unwrap_or_else(|| "N/A".to_string())
385        );
386        println!(
387            "Attribution: {}",
388            depiction.attribution.unwrap_or_else(|| "N/A".to_string())
389        );
390    }
391    println!("----------------------------------------");
392    println!("Total Members: {}", response.members.len());
393}
394
395/// Displays the list of bills in a formatted manner.
396fn display_bills(all_bills: &[cdg_api::response_models::BillSummary]) {
397    println!("Recent Bills:");
398    for bill in all_bills {
399        println!("----------------------------------------");
400        println!(
401            "Bill Number   : {}",
402            bill.number.clone().unwrap_or_else(|| "N/A".to_string())
403        );
404        println!(
405            "Title         : {}",
406            bill.title.clone().unwrap_or_else(|| "N/A".to_string())
407        );
408        println!("Congress      : {}", bill.congress.unwrap_or(0));
409        println!(
410            "Origin Chamber: {}",
411            bill.origin_chamber
412                .clone()
413                .unwrap_or_else(|| "N/A".to_string())
414        );
415        if let Some(action) = &bill.latest_action {
416            println!(
417                "Latest Action : {} on {}",
418                action.text.clone().unwrap_or_else(|| "N/A".to_string()),
419                action
420                    .action_date
421                    .clone()
422                    .unwrap_or_else(|| "N/A".to_string())
423            );
424        } else {
425            println!("Latest Action : N/A");
426        }
427        println!(
428            "URL           : {}",
429            bill.url.clone().unwrap_or_else(|| "N/A".to_string())
430        );
431    }
432    println!("----------------------------------------");
433    println!("Total Bills: {}", all_bills.len());
434}
435
436/// Displays the congress details in a formatted manner.
437fn display_congress_details(response: &CongressDetailsResponse) {
438    let congress = &response.congress;
439    println!("Congress Details:");
440    println!("----------------------------------------");
441    println!(
442        "Name       : {}",
443        congress.name.clone().unwrap_or_else(|| "N/A".to_string())
444    );
445    println!("Number     : {}", congress.number.unwrap_or(0));
446    println!(
447        "Start Year : {}",
448        congress
449            .start_year
450            .clone()
451            .unwrap_or_else(|| "N/A".to_string())
452    );
453    println!(
454        "End Year   : {}",
455        congress
456            .end_year
457            .clone()
458            .unwrap_or_else(|| "N/A".to_string())
459    );
460    println!("Sessions:");
461    if let Some(sessions) = &congress.sessions {
462        for session in sessions {
463            println!(
464                "  - Session {}: {} to {}",
465                session.number.unwrap_or(0),
466                session
467                    .start_date
468                    .clone()
469                    .unwrap_or_else(|| "N/A".to_string()),
470                session
471                    .end_date
472                    .clone()
473                    .unwrap_or_else(|| "N/A".to_string())
474            );
475        }
476    }
477    if let Some(url) = &congress.url {
478        println!("URL        : {}", url.clone());
479    } else {
480        println!("URL        : N/A");
481    }
482    println!("----------------------------------------");
483}
484
485/// Displays the list of nominations in a formatted manner.
486fn display_nominations(response: &NominationsResponse) {
487    println!("Recent Nominations:");
488    for nomination in &response.nominations {
489        println!("----------------------------------------");
490        println!(
491            "Number          : {}",
492            nomination.number.clone().unwrap_or_else(|| 0000)
493        );
494        println!(
495            "Citation        : {}",
496            nomination
497                .citation
498                .clone()
499                .unwrap_or_else(|| "N/A".to_string())
500        );
501        println!(
502            "Description     : {}",
503            nomination
504                .description
505                .clone()
506                .unwrap_or_else(|| "N/A".to_string())
507        );
508        println!(
509            "Received Date   : {}",
510            nomination
511                .received_date
512                .clone()
513                .unwrap_or_else(|| "N/A".to_string())
514        );
515        if let Some(nomination_type) = &nomination.nomination_type {
516            println!(
517                "Nomination Type : is_civilian: {}, is_military: {}",
518                nomination_type.is_civilian.unwrap_or(false),
519                nomination_type.is_military.unwrap_or(false)
520            );
521        } else {
522            println!("Nomination Type : N/A");
523        }
524        if let Some(latest_action) = &nomination.latest_action {
525            println!(
526                "Latest Action   : {}",
527                latest_action
528                    .text
529                    .clone()
530                    .unwrap_or_else(|| "N/A".to_string())
531            );
532        } else {
533            println!("Latest Action   : N/A");
534        }
535        println!(
536            "Organization    : {}",
537            nomination
538                .organization
539                .clone()
540                .unwrap_or_else(|| "N/A".to_string())
541        );
542        println!(
543            "URL             : {}",
544            nomination.url.clone().unwrap_or_else(|| "N/A".to_string())
545        );
546    }
547    println!("----------------------------------------");
548    println!("Total Nominations: {}", response.nominations.len());
549}
550
551/// Displays the list of treaties in a formatted manner.
552fn display_treaties(response: &TreatiesResponse) {
553    println!("Recent Treaties:");
554    for treaty in &response.treaty {
555        println!("----------------------------------------");
556        println!(
557            "Number              : {}",
558            treaty.number.clone().unwrap_or_else(|| 0000)
559        );
560        println!(
561            "Suffix              : {}",
562            treaty.suffix.clone().unwrap_or_else(|| "N/A".to_string())
563        );
564        println!(
565            "Topic               : {}",
566            treaty.topic.clone().unwrap_or_else(|| "N/A".to_string())
567        );
568        println!(
569            "Transmitted Date    : {}",
570            treaty
571                .transmitted_date
572                .clone()
573                .unwrap_or_else(|| "N/A".to_string())
574        );
575        println!(
576            "Resolution Text     : {}",
577            treaty
578                .resolution_text
579                .clone()
580                .unwrap_or_else(|| "N/A".to_string())
581        );
582        println!(
583            "Congress Received   : {}",
584            treaty.congress_received.clone().unwrap_or_else(|| 0000)
585        );
586        println!(
587            "Congress Considered : {}",
588            treaty.congress_considered.clone().unwrap_or_else(|| 0000)
589        );
590        let parts = treaty.parts.clone().unwrap_or_default();
591        println!("Parts Count         : {}", parts.count.unwrap_or(0));
592        if let Some(urls) = &parts.urls {
593            println!("Parts URLs          : {}", urls.join(", "));
594        } else {
595            println!("Parts URLs          : N/A");
596        }
597    }
598    println!("----------------------------------------");
599    println!("Total Treaties: {}", response.treaty.len());
600}
601
602/// Displays the list of committees in a formatted manner.
603fn display_committees(response: &CommitteesResponse) {
604    println!("Congressional Committees:");
605    for committee in &response.committees {
606        println!("----------------------------------------");
607        println!(
608            "Name     : {}",
609            committee.name.clone().unwrap_or_else(|| "N/A".to_string())
610        );
611        println!(
612            "Chamber  : {}",
613            committee
614                .chamber
615                .clone()
616                .unwrap_or_else(|| "N/A".to_string())
617        );
618        println!(
619            "Type     : {}",
620            committee
621                .committee_type_code
622                .clone()
623                .unwrap_or_else(|| "N/A".to_string())
624        );
625        println!(
626            "URL      : {}",
627            committee.url.clone().unwrap_or_else(|| "N/A".to_string())
628        );
629    }
630    println!("----------------------------------------");
631    println!("Total Committees: {}", response.committees.len());
632}
633
634/// Displays the list of laws in a formatted manner.
635fn display_laws(response: &LawsResponse) {
636    println!("Recent Laws:");
637    for law in &response.bills {
638        println!("----------------------------------------");
639        println!(
640            "Law Number    : {}",
641            law.number.clone().unwrap_or_else(|| "N/A".to_string())
642        );
643        println!(
644            "Title         : {}",
645            law.title.clone().unwrap_or_else(|| "N/A".to_string())
646        );
647        println!("Congress      : {}", law.congress.unwrap_or(0));
648        println!(
649            "Origin Chamber: {}",
650            law.origin_chamber
651                .clone()
652                .unwrap_or_else(|| "N/A".to_string())
653        );
654        if let Some(action) = &law.latest_action {
655            println!(
656                "Latest Action : {} on {}",
657                action.text.clone().unwrap_or_else(|| "N/A".to_string()),
658                action
659                    .action_date
660                    .clone()
661                    .unwrap_or_else(|| "N/A".to_string())
662            );
663        } else {
664            println!("Latest Action : N/A");
665        }
666        println!(
667            "URL           : {}",
668            law.url.clone().unwrap_or_else(|| "N/A".to_string())
669        );
670    }
671    println!("----------------------------------------");
672    println!("Total Laws: {}", response.bills.len());
673}
674
675/// Displays the list of amendments in a formatted manner.
676fn display_amendments(response: &AmendmentsResponse) {
677    println!("Recent Amendments:");
678    for amendment in &response.amendments {
679        println!("----------------------------------------");
680        println!(
681            "Amendment Number : {}",
682            amendment
683                .number
684                .clone()
685                .unwrap_or_else(|| "N/A".to_string())
686        );
687        println!(
688            "Type             : {}",
689            amendment
690                .amendment_type
691                .clone()
692                .unwrap_or_else(|| "N/A".to_string())
693        );
694        println!("Congress         : {}", amendment.congress.unwrap_or(0));
695        println!(
696            "Purpose          : {}",
697            amendment
698                .purpose
699                .clone()
700                .unwrap_or_else(|| "N/A".to_string())
701        );
702        println!(
703            "Update Date      : {}",
704            amendment
705                .update_date
706                .clone()
707                .unwrap_or_else(|| "N/A".to_string())
708        );
709        if let Some(action) = &amendment.latest_action {
710            println!(
711                "Latest Action    : {} on {}",
712                action.text.clone().unwrap_or_else(|| "N/A".to_string()),
713                action
714                    .action_date
715                    .clone()
716                    .unwrap_or_else(|| "N/A".to_string())
717            );
718        } else {
719            println!("Latest Action    : N/A");
720        }
721        println!(
722            "URL              : {}",
723            amendment.url.clone().unwrap_or_else(|| "N/A".to_string())
724        );
725    }
726    println!("----------------------------------------");
727    println!("Total Amendments: {}", response.amendments.len());
728}
729
730/// Displays detailed information about a specific member.
731fn display_member_details(response: &MemberDetailsResponse) {
732    let member = &response.member;
733    println!("Member Details:");
734    println!("----------------------------------------");
735    println!(
736        "Name               : {} {} {}",
737        member.first_name.clone().unwrap_or_else(|| "".to_string()),
738        member.middle_name.clone().unwrap_or_else(|| "".to_string()),
739        member.last_name.clone().unwrap_or_else(|| "".to_string())
740    );
741    println!(
742        "Suffix             : {}",
743        member
744            .suffix_name
745            .clone()
746            .unwrap_or_else(|| "N/A".to_string())
747    );
748    println!(
749        "Nickname           : {}",
750        member
751            .nick_name
752            .clone()
753            .unwrap_or_else(|| "N/A".to_string())
754    );
755    println!(
756        "Honorific Name     : {}",
757        member
758            .honorific_name
759            .clone()
760            .unwrap_or_else(|| "N/A".to_string())
761    );
762    println!(
763        "Bioguide ID        : {}",
764        member
765            .bioguide_id
766            .clone()
767            .unwrap_or_else(|| "N/A".to_string())
768    );
769    println!(
770        "Official URL       : {}",
771        member
772            .official_website_url
773            .clone()
774            .unwrap_or_else(|| "N/A".to_string())
775    );
776    if let Some(address) = &member.address_information {
777        println!(
778            "Office Address     : {}",
779            address
780                .office_address
781                .clone()
782                .unwrap_or_else(|| "N/A".to_string())
783        );
784        println!(
785            "City               : {}",
786            address.city.clone().unwrap_or_else(|| "N/A".to_string())
787        );
788        println!(
789            "District           : {}",
790            address
791                .district
792                .clone()
793                .unwrap_or_else(|| "N/A".to_string())
794        );
795        println!("ZIP Code           : {}", address.zip_code.unwrap_or(00000));
796        println!(
797            "Phone Number       : {}",
798            address
799                .phone_number
800                .clone()
801                .unwrap_or_else(|| "N/A".to_string())
802        );
803    } else {
804        println!("Address Information: N/A");
805    }
806    if let Some(depiction) = &member.depiction {
807        println!(
808            "Image URL          : {}",
809            depiction
810                .image_url
811                .clone()
812                .unwrap_or_else(|| "N/A".to_string())
813        );
814        println!(
815            "Attribution        : {}",
816            depiction
817                .attribution
818                .clone()
819                .unwrap_or_else(|| "N/A".to_string())
820        );
821    } else {
822        println!("Depiction          : N/A");
823    }
824    println!("\nParty Affiliation:");
825    if let Some(party_history) = &member.party_history {
826        for party in party_history {
827            println!("----------------------------------------");
828            println!(
829                "  - Party: {}",
830                party
831                    .party_name
832                    .clone()
833                    .unwrap_or_else(|| "N/A".to_string())
834            );
835            println!("    Start: {}", party.start_year.unwrap_or(0));
836            println!("    End  : {}", party.end_year.unwrap_or(0));
837        }
838    } else {
839        println!("Party: N/A");
840    }
841    println!("\nTerms of Service:");
842    if let Some(terms) = &member.terms {
843        for term in terms {
844            println!("----------------------------------------");
845            println!(
846                "  - Chamber: {}",
847                term.chamber.clone().unwrap_or_else(|| "N/A".to_string())
848            );
849            println!("    Congress: {}", term.congress.unwrap_or(0));
850            println!(
851                "    State: {} ({})",
852                term.state_name.clone().unwrap_or_else(|| "N/A".to_string()),
853                term.state_code.clone().unwrap_or_else(|| "N/A".to_string())
854            );
855            println!(
856                "    Party: {} ({})",
857                term.party_name.clone().unwrap_or_else(|| "N/A".to_string()),
858                term.party_code.clone().unwrap_or_else(|| "N/A".to_string())
859            );
860            println!(
861                "    Term: {} - {}",
862                term.start_year.unwrap_or(0),
863                term.end_year.unwrap_or(0)
864            );
865        }
866    } else {
867        println!("Terms: N/A");
868    }
869    println!("\n");
870    println!(
871        "Sponsored Legislation: {} bills",
872        member
873            .sponsored_legislation
874            .clone()
875            .unwrap_or_default()
876            .count
877            .unwrap_or(0)
878    );
879    println!(
880        "Cosponsored Legislation: {} bills",
881        member
882            .cosponsored_legislation
883            .clone()
884            .unwrap_or_default()
885            .count
886            .unwrap_or(0)
887    );
888    println!("----------------------------------------");
889}
890
891fn display_billacts_details(response: &BillActionsResponse) {
892    println!("Bill Actions:");
893    for action in &response.actions {
894        println!("----------------------------------------");
895        println!(
896            "Action Code: {}",
897            action
898                .action_code
899                .clone()
900                .unwrap_or_else(|| "N/A".to_string())
901        );
902        println!(
903            "Action Date: {}",
904            action
905                .action_date
906                .clone()
907                .unwrap_or_else(|| "N/A".to_string())
908        );
909        println!(
910            "Action Time: {}",
911            action
912                .action_time
913                .clone()
914                .unwrap_or_else(|| "N/A".to_string())
915        );
916        println!(
917            "Action Type: {}",
918            action
919                .action_type
920                .clone()
921                .unwrap_or_else(|| "N/A".to_string())
922        );
923        println!(
924            "Text       : {}",
925            action.text.clone().unwrap_or_else(|| "N/A".to_string())
926        );
927        if let Some(ref source_system) = action.source_system {
928            println!("---> Source System Details");
929            println!(
930                "source system           : {}",
931                source_system.name.clone().unwrap_or_default()
932            );
933            println!(
934                "source system code           : {}",
935                source_system.code.clone().unwrap_or_default()
936            );
937        }
938        if let Some(ref committees) = action.committees {
939            println!("---> Committees Details");
940            for committee in committees {
941                println!(
942                    "committee name           : {}",
943                    committee.name.clone().unwrap_or_default()
944                );
945                println!(
946                    "committee code           : {}",
947                    committee.system_code.clone().unwrap_or_default()
948                );
949                println!(
950                    "committee chamber         : {}",
951                    committee.chamber.clone().unwrap_or_default()
952                );
953                println!(
954                    "committee type    : {}",
955                    committee.committee_type.clone().unwrap_or_default()
956                );
957                if let Some(activities) = &committee.activities {
958                    println!("---> Committee Activities Details");
959                    for activity in activities {
960                        println!(
961                            "activity name           : {}",
962                            activity.name.clone().unwrap_or_default()
963                        );
964                        println!(
965                            "activity date           : {}",
966                            activity.date.clone().unwrap_or_default()
967                        );
968                    }
969                }
970                println!("url        : {}", committee.url.clone().unwrap_or_default());
971            }
972        }
973        if let Some(recorded_votes) = &action.recorded_votes {
974            println!("---> Recorded Votes Details");
975            for vote in recorded_votes {
976                println!(
977                    "vote chamber           : {}",
978                    vote.chamber.clone().unwrap_or_default()
979                );
980                println!(
981                    "vote congress           : {}",
982                    vote.congress.clone().unwrap_or_default()
983                );
984                println!(
985                    "vote session           : {}",
986                    vote.session_number.clone().unwrap_or_default()
987                );
988                println!(
989                    "roll call number           : {}",
990                    vote.roll_number.clone().unwrap_or_default()
991                );
992                println!(
993                    "vote date           : {}",
994                    vote.date.clone().unwrap_or_default()
995                );
996                println!("url           : {}", vote.url.clone().unwrap_or_default());
997            }
998        }
999        if let Some(unknown) = &action.unknown {
1000            println!("---> Unknown Details");
1001            println!("{:#?}", unknown);
1002        }
1003    }
1004    println!("----------------------------------------");
1005}
1006
1007/// Displays detailed information about a specific bill.
1008fn display_bill_details(response: &BillDetailsResponse) {
1009    let bill = &response.bill;
1010    println!("\nBill Details:");
1011    println!("----------------------------------------");
1012    println!(
1013        "Number             : {}",
1014        bill.number.clone().unwrap_or_else(|| "N/A".to_string())
1015    );
1016    println!(
1017        "Title              : {}",
1018        bill.title.clone().unwrap_or_else(|| "N/A".to_string())
1019    );
1020    println!("Congress           : {}", bill.congress.unwrap_or(0));
1021    println!(
1022        "Origin Chamber     : {}",
1023        bill.origin_chamber
1024            .clone()
1025            .unwrap_or_else(|| "N/A".to_string())
1026    );
1027    println!(
1028        "Last Update Date   : {}",
1029        bill.update_date
1030            .clone()
1031            .unwrap_or_else(|| "N/A".to_string())
1032    );
1033    println!(
1034        "Laws Associated    : {} laws",
1035        bill.related_bills
1036            .clone()
1037            .unwrap_or_default()
1038            .count
1039            .unwrap_or(0)
1040    );
1041    println!("Latest Action:");
1042    if let Some(action) = &bill.latest_action {
1043        println!(
1044            "  - Text       : {}",
1045            action.text.clone().unwrap_or_else(|| "N/A".to_string())
1046        );
1047        println!(
1048            "  - Action Date: {}",
1049            action
1050                .action_date
1051                .clone()
1052                .unwrap_or_else(|| "N/A".to_string())
1053        );
1054    } else {
1055        println!("  - Latest Action: N/A");
1056    }
1057    println!("----------------------------------------");
1058
1059    println!("Raw JSON Response:");
1060    println!("{:#?}", response);
1061}