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