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 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}