Skip to main content

haste_health/commands/
api.rs

1#![allow(unused)]
2use crate::CLIState;
3use clap::Subcommand;
4use haste_fhir_client::{
5    FHIRClient,
6    http::{FHIRHttpClient, FHIRHttpState},
7    url::ParsedParameters,
8};
9use haste_fhir_model::r4::generated::{
10    resources::{Bundle, Resource, ResourceType},
11    terminology::IssueType,
12};
13use haste_fhir_operation_error::OperationOutcomeError;
14use haste_server::auth_n::oidc::routes::discovery::WellKnownDiscoveryDocument;
15use std::sync::Arc;
16use tokio::sync::Mutex;
17
18#[derive(Subcommand, Debug)]
19pub enum ApiCommands {
20    Create {
21        #[arg(short, long)]
22        data: Option<String>,
23        #[arg(short, long)]
24        file: Option<String>,
25        resource_type: String,
26    },
27    Read {
28        resource_type: String,
29        id: String,
30    },
31
32    VersionRead {
33        resource_type: String,
34        id: String,
35        version_id: String,
36    },
37
38    Patch {
39        #[arg(short, long)]
40        data: Option<String>,
41        #[arg(short, long)]
42        file: Option<String>,
43        resource_type: String,
44        id: String,
45    },
46    Update {
47        #[arg(short, long)]
48        data: Option<String>,
49        #[arg(short, long)]
50        file: Option<String>,
51        resource_type: String,
52        id: String,
53    },
54    Transaction {
55        #[arg(short, long)]
56        data: Option<String>,
57        #[arg(short, long)]
58        parallel: Option<usize>,
59        #[arg(short, long)]
60        file: Option<String>,
61        #[arg(short, long)]
62        output: Option<bool>,
63    },
64    Batch {
65        #[arg(short, long)]
66        data: Option<String>,
67        #[arg(short, long)]
68        file: Option<String>,
69        #[arg(short, long)]
70        output: Option<bool>,
71    },
72
73    HistorySystem {
74        parameters: Option<String>,
75    },
76
77    HistoryType {
78        resource_type: String,
79        parameters: Option<String>,
80    },
81
82    HistoryInstance {
83        resource_type: String,
84        id: String,
85        parameters: Option<String>,
86    },
87
88    SearchType {
89        resource_type: String,
90        parameters: Option<String>,
91    },
92
93    SearchSystem {
94        parameters: Option<String>,
95    },
96
97    InvokeSystem {
98        #[arg(short, long)]
99        data: Option<String>,
100        #[arg(short, long)]
101        file: Option<String>,
102        operation_name: String,
103    },
104
105    InvokeType {
106        #[arg(short, long)]
107        data: Option<String>,
108        #[arg(short, long)]
109        file: Option<String>,
110        resource_type: String,
111        operation_name: String,
112    },
113
114    Capabilities {},
115
116    DeleteInstance {
117        resource_type: String,
118        id: String,
119    },
120
121    DeleteType {
122        resource_type: String,
123        parameters: Option<String>,
124    },
125
126    DeleteSystem {
127        parameters: Option<String>,
128    },
129
130    InvokeInstance {
131        #[arg(short, long)]
132        data: Option<String>,
133        #[arg(short, long)]
134        file: Option<String>,
135        resource_type: String,
136        id: String,
137        operation_name: String,
138    },
139}
140
141async fn derive_resource_data_arg_file_arg_or_stdin<Type: serde::de::DeserializeOwned>(
142    data_arg: &Option<String>,
143    file_path: &Option<String>,
144) -> Result<Type, OperationOutcomeError> {
145    if let Some(data) = data_arg {
146        serde_json::from_str::<Type>(data).map_err(|e| {
147            OperationOutcomeError::error(
148                IssueType::Exception(None),
149                format!("Failed to parse transaction data: {}", e),
150            )
151        })
152    } else if let Some(file_path) = file_path {
153        let file_content = std::fs::read_to_string(file_path).map_err(|e| {
154            OperationOutcomeError::error(
155                IssueType::Exception(None),
156                format!("Failed to read transaction file: {}", e),
157            )
158        })?;
159
160        serde_json::from_str::<Type>(&file_content).map_err(|e| {
161            OperationOutcomeError::error(
162                IssueType::Exception(None),
163                format!("Failed to parse file: {}", e),
164            )
165        })
166    } else {
167        // Read from stdin
168        let mut buffer = String::new();
169
170        std::io::stdin().read_line(&mut buffer).map_err(|e| {
171            OperationOutcomeError::error(
172                IssueType::Exception(None),
173                format!("Failed to read from stdin: {}", e),
174            )
175        })?;
176
177        serde_json::from_str::<Type>(&buffer).map_err(|e| {
178            OperationOutcomeError::error(
179                IssueType::Exception(None),
180                format!("Failed to parse transaction from stdin: {}", e),
181            )
182        })
183    }
184}
185
186pub async fn api_commands(
187    state: Arc<Mutex<CLIState>>,
188    command: &ApiCommands,
189) -> Result<(), OperationOutcomeError> {
190    let fhir_client = crate::client::fhir_client(state).await?;
191
192    match command {
193        ApiCommands::Create {
194            data,
195            resource_type,
196            file,
197        } => {
198            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
199                OperationOutcomeError::error(
200                    IssueType::Invalid(None),
201                    format!(
202                        "'{}' is not a valid FHIR resource type: {}",
203                        resource_type, e
204                    ),
205                )
206            })?;
207
208            let resource =
209                derive_resource_data_arg_file_arg_or_stdin::<Resource>(data, file).await?;
210
211            let result = fhir_client.create((), resource_type, resource).await?;
212
213            println!(
214                "{}",
215                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
216            );
217
218            Ok(())
219        }
220        ApiCommands::Read { resource_type, id } => {
221            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
222                OperationOutcomeError::error(
223                    IssueType::Invalid(None),
224                    format!(
225                        "'{}' is not a valid FHIR resource type: {}",
226                        resource_type, e
227                    ),
228                )
229            })?;
230
231            let result = fhir_client.read((), resource_type, id.clone()).await?;
232
233            println!(
234                "{}",
235                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
236            );
237
238            Ok(())
239        }
240        ApiCommands::Patch {
241            resource_type,
242            id,
243            data,
244            file,
245        } => {
246            let patches = if let Some(file) = file {
247                let file_content = std::fs::read_to_string(file).map_err(|e| {
248                    OperationOutcomeError::error(
249                        IssueType::Exception(None),
250                        format!("Failed to read transaction file: {}", e),
251                    )
252                })?;
253
254                serde_json::from_str::<json_patch::Patch>(&file_content).map_err(|e| {
255                    OperationOutcomeError::error(
256                        IssueType::Invalid(None),
257                        format!("Failed to parse patch JSON: {}", e),
258                    )
259                })?
260            } else if let Some(data) = data {
261                serde_json::from_str::<json_patch::Patch>(&data).map_err(|e| {
262                    OperationOutcomeError::error(
263                        IssueType::Invalid(None),
264                        format!("Failed to parse patch JSON: {}", e),
265                    )
266                })?
267            } else {
268                return Err(OperationOutcomeError::error(
269                    IssueType::Invalid(None),
270                    "Either --data or --file must be provided for patch operation.".to_string(),
271                ));
272            };
273
274            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
275                OperationOutcomeError::error(
276                    IssueType::Invalid(None),
277                    format!(
278                        "'{}' is not a valid FHIR resource type: {}",
279                        resource_type, e
280                    ),
281                )
282            })?;
283
284            let result = fhir_client
285                .patch((), resource_type, id.clone(), patches)
286                .await?;
287
288            println!(
289                "{}",
290                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
291            );
292
293            Ok(())
294        }
295        ApiCommands::Update {
296            resource_type,
297            id,
298            data,
299            file,
300        } => {
301            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
302                OperationOutcomeError::error(
303                    IssueType::Invalid(None),
304                    format!(
305                        "'{}' is not a valid FHIR resource type: {}",
306                        resource_type, e
307                    ),
308                )
309            })?;
310
311            let resource =
312                derive_resource_data_arg_file_arg_or_stdin::<Resource>(data, file).await?;
313
314            let result = fhir_client
315                .update((), resource_type, id.clone(), resource)
316                .await?;
317
318            println!(
319                "{}",
320                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
321            );
322            Ok(())
323        }
324        ApiCommands::Transaction {
325            data,
326            file,
327            output,
328            parallel,
329        } => {
330            let bundle = derive_resource_data_arg_file_arg_or_stdin::<Bundle>(data, file).await?;
331
332            let parallel = parallel.unwrap_or(1);
333
334            let mut futures = tokio::task::JoinSet::new();
335
336            for _ in 0..parallel {
337                let client = fhir_client.clone();
338                let bundle = bundle.clone();
339                let res = async move { client.transaction((), bundle).await };
340                futures.spawn(res);
341            }
342
343            let res = futures.join_all().await;
344
345            for bundle_result in res {
346                let bundle = bundle_result?;
347                if let Some(true) = output {
348                    println!(
349                        "{}",
350                        serde_json::to_string_pretty(&bundle)
351                            .expect("Failed to serialize response")
352                    );
353                }
354            }
355
356            Ok(())
357        }
358        ApiCommands::VersionRead {
359            resource_type,
360            id,
361            version_id,
362        } => {
363            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
364                OperationOutcomeError::error(
365                    IssueType::Invalid(None),
366                    format!(
367                        "'{}' is not a valid FHIR resource type: {}",
368                        resource_type, e
369                    ),
370                )
371            })?;
372
373            let result = fhir_client
374                .vread((), resource_type, id.clone(), version_id.clone())
375                .await?;
376
377            println!(
378                "{}",
379                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
380            );
381
382            Ok(())
383        }
384        ApiCommands::Batch { data, file, output } => {
385            let bundle = derive_resource_data_arg_file_arg_or_stdin::<Bundle>(data, file).await?;
386
387            let result = fhir_client.batch((), bundle).await?;
388
389            if let Some(true) = output {
390                println!(
391                    "{}",
392                    serde_json::to_string_pretty(&result).expect("Failed to serialize response")
393                );
394            }
395
396            Ok(())
397        }
398        ApiCommands::HistorySystem { parameters } => {
399            let result = fhir_client
400                .history_system(
401                    (),
402                    ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?,
403                )
404                .await?;
405
406            println!(
407                "{}",
408                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
409            );
410
411            Ok(())
412        }
413        ApiCommands::HistoryType {
414            resource_type,
415            parameters,
416        } => {
417            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
418                OperationOutcomeError::error(
419                    IssueType::Invalid(None),
420                    format!(
421                        "'{}' is not a valid FHIR resource type: {}",
422                        resource_type, e
423                    ),
424                )
425            })?;
426
427            let result = fhir_client
428                .history_type(
429                    (),
430                    resource_type,
431                    ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?,
432                )
433                .await?;
434
435            println!(
436                "{}",
437                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
438            );
439
440            Ok(())
441        }
442        ApiCommands::HistoryInstance {
443            resource_type,
444            id,
445            parameters,
446        } => {
447            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
448                OperationOutcomeError::error(
449                    IssueType::Invalid(None),
450                    format!(
451                        "'{}' is not a valid FHIR resource type: {}",
452                        resource_type, e
453                    ),
454                )
455            })?;
456
457            let result = fhir_client
458                .history_instance(
459                    (),
460                    resource_type,
461                    id.clone(),
462                    ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?,
463                )
464                .await?;
465
466            println!(
467                "{}",
468                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
469            );
470
471            Ok(())
472        }
473        ApiCommands::SearchType {
474            resource_type,
475            parameters,
476        } => {
477            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
478                OperationOutcomeError::error(
479                    IssueType::Invalid(None),
480                    format!(
481                        "'{}' is not a valid FHIR resource type: {}",
482                        resource_type, e
483                    ),
484                )
485            })?;
486
487            let result = fhir_client
488                .search_type(
489                    (),
490                    resource_type,
491                    ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?,
492                )
493                .await?;
494
495            println!(
496                "{}",
497                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
498            );
499
500            Ok(())
501        }
502        ApiCommands::SearchSystem { parameters } => {
503            let result = fhir_client
504                .search_system(
505                    (),
506                    ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?,
507                )
508                .await?;
509
510            println!(
511                "{}",
512                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
513            );
514
515            Ok(())
516        }
517        ApiCommands::InvokeSystem {
518            operation_name,
519            file,
520            data,
521        } => {
522            let parameters = derive_resource_data_arg_file_arg_or_stdin::<
523                haste_fhir_model::r4::generated::resources::Parameters,
524            >(data, file)
525            .await?;
526
527            let result = fhir_client
528                .invoke_system((), operation_name.clone(), parameters)
529                .await?;
530
531            println!(
532                "{}",
533                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
534            );
535
536            Ok(())
537        }
538        ApiCommands::InvokeType {
539            resource_type,
540            operation_name,
541            file,
542            data,
543        } => {
544            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
545                OperationOutcomeError::error(
546                    IssueType::Invalid(None),
547                    format!(
548                        "'{}' is not a valid FHIR resource type: {}",
549                        resource_type, e
550                    ),
551                )
552            })?;
553
554            let parameters = derive_resource_data_arg_file_arg_or_stdin::<
555                haste_fhir_model::r4::generated::resources::Parameters,
556            >(data, file)
557            .await?;
558
559            let result = fhir_client
560                .invoke_type((), resource_type, operation_name.clone(), parameters)
561                .await?;
562
563            println!(
564                "{}",
565                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
566            );
567
568            Ok(())
569        }
570        ApiCommands::InvokeInstance {
571            resource_type,
572            id,
573            operation_name,
574            file,
575            data,
576        } => {
577            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
578                OperationOutcomeError::error(
579                    IssueType::Invalid(None),
580                    format!(
581                        "'{}' is not a valid FHIR resource type: {}",
582                        resource_type, e
583                    ),
584                )
585            })?;
586
587            let parameters = derive_resource_data_arg_file_arg_or_stdin::<
588                haste_fhir_model::r4::generated::resources::Parameters,
589            >(data, file)
590            .await?;
591
592            let result = fhir_client
593                .invoke_instance(
594                    (),
595                    resource_type,
596                    id.clone(),
597                    operation_name.clone(),
598                    parameters,
599                )
600                .await?;
601
602            println!(
603                "{}",
604                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
605            );
606
607            Ok(())
608        }
609        ApiCommands::Capabilities {} => {
610            let result = fhir_client.capabilities(()).await?;
611
612            println!(
613                "{}",
614                serde_json::to_string_pretty(&result).expect("Failed to serialize response")
615            );
616
617            Ok(())
618        }
619        ApiCommands::DeleteInstance { resource_type, id } => {
620            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
621                OperationOutcomeError::error(
622                    IssueType::Invalid(None),
623                    format!(
624                        "'{}' is not a valid FHIR resource type: {}",
625                        resource_type, e
626                    ),
627                )
628            })?;
629
630            fhir_client
631                .delete_instance((), resource_type.clone(), id.clone())
632                .await?;
633
634            println!(
635                "Resource of type '{}' with ID '{}' deleted.",
636                resource_type.as_ref(),
637                id
638            );
639
640            Ok(())
641        }
642        ApiCommands::DeleteType {
643            resource_type,
644            parameters,
645        } => {
646            let resource_type = ResourceType::try_from(resource_type.as_str()).map_err(|e| {
647                OperationOutcomeError::error(
648                    IssueType::Invalid(None),
649                    format!(
650                        "'{}' is not a valid FHIR resource type: {}",
651                        resource_type, e
652                    ),
653                )
654            })?;
655
656            let parsed_parameters =
657                ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?;
658
659            fhir_client
660                .delete_type((), resource_type.clone(), parsed_parameters)
661                .await?;
662
663            println!(
664                "Resources of type '{}' deleted based on provided parameters.",
665                resource_type.as_ref()
666            );
667
668            Ok(())
669        }
670        ApiCommands::DeleteSystem { parameters } => {
671            let parsed_parameters =
672                ParsedParameters::try_from(parameters.clone().unwrap_or_default().as_str())?;
673
674            fhir_client.delete_system((), parsed_parameters).await?;
675
676            println!("Resources deleted based on provided system-level parameters.");
677
678            Ok(())
679        }
680    }
681}