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