rh-foundation 0.1.0-beta.1

Foundation crate providing common utilities, error handling, and shared functionality
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# FHIR Package Loader

The FHIR Package Loader provides a robust, standards-compliant implementation for downloading and managing FHIR packages from npm-style registries.

## Overview

The loader enables applications to:
- Download FHIR packages from npm-style registries (e.g., packages.fhir.org)
- Cache packages locally in a standardized directory structure
- Support authentication for private registries
- Maintain compatibility with HL7 tooling ecosystem

## Design Philosophy

### Compatibility with HL7 Tooling

The loader is designed to be fully compatible with the HL7 FHIR tooling ecosystem:

- **Standard Package Directory**: Uses `~/.fhir/packages/` by default, matching the Java validator, HAPI FHIR, and other HL7 tools
- **Package Naming Convention**: Follows the `{package-id}#{version}` format for directory names
- **Package Manifest**: Reads and writes standard `package.json` manifests
- **Registry Protocol**: Implements the npm-style registry protocol used by packages.fhir.org

This ensures that:
- Packages downloaded by rh can be used by other HL7 tools
- Packages cached by other tools can be discovered and used by rh
- Teams can share a single package cache across multiple tools

### Private Registry Support

The loader supports private FHIR package registries through flexible authentication:

```rust
use rh_foundation::loader::{PackageLoader, LoaderConfig};

let config = LoaderConfig {
    registry_url: "https://private-registry.example.com".to_string(),
    auth_token: Some("your-token-here".to_string()),
    ..Default::default()
};

let loader = PackageLoader::new(config);
```

**Authentication Methods:**
- Bearer token authentication for private registries
- Configurable per-registry credentials
- Secure token handling (never logged or displayed)

**Use Cases:**
- Enterprise-specific FHIR profiles
- Pre-release package versions
- Proprietary implementation guides
- Organization-specific extensions

### Caching Strategy

The loader implements an efficient multi-layer caching strategy:

#### 1. Package Directory Cache

Downloaded packages are cached in the filesystem:
```
~/.fhir/packages/
├── hl7.fhir.r4.core#4.0.1/
│   ├── package.json
│   ├── package/
│   │   ├── StructureDefinition-Patient.json
│   │   ├── ValueSet-administrative-gender.json
│   │   └── ...
├── hl7.fhir.us.core#5.0.1/
│   └── ...
└── custom.organization.profiles#1.0.0/
    └── ...
```

**Benefits:**
- Shared cache across all rh applications
- Persistent storage survives application restarts
- Compatible with other HL7 tools
- Reduces network bandwidth usage

#### 2. Registry Metadata Cache

Package manifests are cached to avoid repeated registry lookups:

```rust
// First lookup: queries registry
let manifest1 = loader.get_package_manifest("hl7.fhir.r4.core", "4.0.1").await?;

// Second lookup: uses cached manifest
let manifest2 = loader.get_package_manifest("hl7.fhir.r4.core", "4.0.1").await?;
```

**Cached Information:**
- Package metadata (name, version, description)
- Dependency lists
- File checksums
- Download URLs

#### 3. In-Memory Cache

Frequently accessed packages are kept in memory for immediate access:
- Reduces disk I/O
- Speeds up repeated validations
- Configurable memory limits
- LRU eviction policy

### Network Optimization

The loader is optimized for network efficiency:

**Parallel Downloads:**
```rust
// Downloads dependencies in parallel
loader.download_package_with_dependencies("hl7.fhir.us.core", "5.0.1").await?;
```

**Resumable Downloads:**
- Supports HTTP range requests
- Can resume interrupted downloads
- Validates checksums on completion

**Bandwidth Management:**
- Configurable timeouts
- Connection pooling
- Retry with exponential backoff

## Architecture

### Core Components

```
┌─────────────────────────────────────────────────────────┐
│                    PackageLoader                         │
│  - Registry communication                                │
│  - Authentication handling                               │
│  - Cache coordination                                    │
└─────────────┬───────────────────────────────────────────┘
              ├──► RegistryClient
              │    - HTTP requests to registry
              │    - Version resolution
              │    - Manifest retrieval
              ├──► PackageCache
              │    - Filesystem operations
              │    - Package extraction
              │    - Directory management
              └──► DownloadManager
                   - Tarball retrieval
                   - Checksum verification
                   - Parallel downloads
```

### Data Flow

```
┌──────────┐
│  Request │  download_package("hl7.fhir.r4.core", "4.0.1")
└────┬─────┘
┌────────────────┐
│ Check Cache    │ ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/
└────┬───────────┘
     ├─── Found ──────────► Return cached package
     └─── Not Found
     ┌────────────────┐
     │ Query Registry │ GET https://packages.fhir.org/hl7.fhir.r4.core/4.0.1
     └────┬───────────┘
     ┌────────────────┐
     │ Download       │ GET tarball URL
     │ & Extract      │ Verify checksum
     └────┬───────────┘
     ┌────────────────┐
     │ Cache Package  │ Save to ~/.fhir/packages/
     └────┬───────────┘
     Return package
```

## Configuration

### Default Configuration

```rust
use rh_foundation::loader::{PackageLoader, LoaderConfig};

// Uses default settings
let loader = PackageLoader::default();
```

**Defaults:**
- Registry: `https://packages.fhir.org`
- Package directory: `~/.fhir/packages`
- Timeout: 30 seconds
- No authentication

### Custom Configuration

```rust
let config = LoaderConfig {
    registry_url: "https://custom-registry.example.com".to_string(),
    packages_dir: Some("/custom/path/packages".into()),
    auth_token: Some("token123".to_string()),
    timeout_secs: Some(60),
    user_agent: Some("MyApp/1.0".to_string()),
};

let loader = PackageLoader::new(config);
```

### Environment Variables

The loader respects standard environment variables:

- `FHIR_PACKAGES_DIR`: Override default package directory
- `FHIR_REGISTRY_URL`: Override default registry URL
- `FHIR_AUTH_TOKEN`: Provide authentication token

## Usage Examples

### Basic Package Download

```rust
use rh_foundation::loader::PackageLoader;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let loader = PackageLoader::default();
    
    // Download a specific version
    let package_dir = loader
        .download_package("hl7.fhir.r4.core", "4.0.1")
        .await?;
    
    println!("Package downloaded to: {}", package_dir.display());
    
    Ok(())
}
```

### Working with Package Manifests

```rust
use rh_foundation::loader::PackageLoader;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let loader = PackageLoader::default();
    
    // Get package metadata
    let manifest = loader
        .get_package_manifest("hl7.fhir.us.core", "5.0.1")
        .await?;
    
    println!("Package: {}", manifest.name);
    println!("Version: {}", manifest.version);
    println!("Description: {}", manifest.description.unwrap_or_default());
    
    // List dependencies
    if let Some(deps) = manifest.dependencies {
        println!("\nDependencies:");
        for (name, version) in deps {
            println!("  {} @ {}", name, version);
        }
    }
    
    Ok(())
}
```

### Download with Dependencies

```rust
use rh_foundation::loader::PackageLoader;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let loader = PackageLoader::default();
    
    // Downloads package and all its dependencies
    loader
        .download_package_with_dependencies("hl7.fhir.us.core", "5.0.1")
        .await?;
    
    println!("Package and dependencies downloaded!");
    
    Ok(())
}
```

### Private Registry with Authentication

```rust
use rh_foundation::loader::{PackageLoader, LoaderConfig};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = LoaderConfig {
        registry_url: "https://private.example.com".to_string(),
        auth_token: Some(std::env::var("PRIVATE_REGISTRY_TOKEN")?),
        ..Default::default()
    };
    
    let loader = PackageLoader::new(config);
    
    let package_dir = loader
        .download_package("my.org.profiles", "1.0.0")
        .await?;
    
    Ok(())
}
```

### Listing Available Versions

```rust
use rh_foundation::loader::PackageLoader;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let loader = PackageLoader::default();
    
    // Query registry for available versions
    let versions = loader
        .list_package_versions("hl7.fhir.r4.core")
        .await?;
    
    println!("Available versions:");
    for version in versions {
        println!("  - {}", version);
    }
    
    Ok(())
}
```

## Error Handling

The loader provides detailed error types for different failure scenarios:

```rust
use rh_foundation::loader::{PackageLoader, LoaderError};

#[tokio::main]
async fn main() {
    let loader = PackageLoader::default();
    
    match loader.download_package("invalid.package", "1.0.0").await {
        Ok(path) => println!("Downloaded to: {}", path.display()),
        Err(LoaderError::PackageNotFound { package, version }) => {
            eprintln!("Package {}#{} not found in registry", package, version);
        }
        Err(LoaderError::NetworkError(e)) => {
            eprintln!("Network error: {}", e);
        }
        Err(LoaderError::AuthenticationFailed) => {
            eprintln!("Authentication failed - check your token");
        }
        Err(e) => eprintln!("Error: {}", e),
    }
}
```

## Performance Characteristics

### Benchmark Results

Typical performance on a modern system with good network connectivity:

| Operation | First Run | Cached |
|-----------|-----------|--------|
| Download hl7.fhir.r4.core#4.0.1 | ~15s | <1ms |
| Download with dependencies | ~45s | <10ms |
| List package versions | ~500ms | ~50ms |
| Get package manifest | ~300ms | <1ms |

### Memory Usage

- Base loader: ~2 MB
- Per cached package manifest: ~5-10 KB
- In-memory package cache: Configurable (default: 100 MB limit)

### Disk Usage

- Core FHIR packages: ~50-100 MB each
- US Core: ~5-10 MB
- Typical workspace with 5-10 packages: ~500 MB

## Testing

The loader includes comprehensive tests:

```bash
# Run loader tests
cargo test -p rh-foundation --lib loader

# Run with HTTP feature
cargo test -p rh-foundation --features http loader
```

### Test Coverage

- ✅ Package download and extraction
- ✅ Cache hit/miss scenarios
- ✅ Authentication handling
- ✅ Network error recovery
- ✅ Concurrent downloads
- ✅ Manifest parsing
- ✅ Directory creation and cleanup

## Integration with Other Tools

### HAPI FHIR Validator

The loader is compatible with the HAPI FHIR Validator's package cache:

```bash
# Packages downloaded by rh are available to HAPI
rh download-package hl7.fhir.us.core 5.0.1

# HAPI can now use the cached package
java -jar validator_cli.jar -ig hl7.fhir.us.core#5.0.1 patient.json
```

### FHIR Shorthand (FSH) Sushi

Works seamlessly with Sushi's package management:

```yaml
# sushi-config.yaml
dependencies:
  hl7.fhir.us.core: 5.0.1
```

Both Sushi and rh will use the same cached packages in `~/.fhir/packages/`.

### VS Code FHIR Extension

The VS Code FHIR extension can discover and use packages downloaded by rh.

## Troubleshooting

### Package Not Found

**Error:** Package not found in registry

**Solutions:**
- Verify package name and version
- Check network connectivity
- Ensure registry URL is correct
- Try listing available versions

### Authentication Issues

**Error:** Authentication failed

**Solutions:**
- Verify auth token is valid and not expired
- Check token has correct permissions
- Ensure token is properly formatted (no extra whitespace)
- Test with curl: `curl -H "Authorization: Bearer TOKEN" REGISTRY_URL`

### Cache Corruption

**Error:** Invalid package in cache

**Solutions:**
```bash
# Clear cache for specific package
rm -rf ~/.fhir/packages/hl7.fhir.r4.core#4.0.1

# Clear entire cache
rm -rf ~/.fhir/packages/*
```

### Permission Errors

**Error:** Cannot write to package directory

**Solutions:**
- Check directory permissions: `ls -la ~/.fhir/packages`
- Fix permissions: `chmod 755 ~/.fhir/packages`
- Override directory with `FHIR_PACKAGES_DIR` environment variable

## Future Enhancements

Planned improvements:

- [ ] Offline mode with stale cache tolerance
- [ ] Delta downloads for package updates
- [ ] Package integrity verification with signatures
- [ ] Peer-to-peer package sharing
- [ ] Package pruning and cleanup utilities
- [ ] Mirror/proxy server support
- [ ] Package dependency visualization
- [ ] Version constraint resolution (^, ~, >=)

## References

- [FHIR Package Registry Specification]https://confluence.hl7.org/display/FHIR/NPM+Package+Specification
- [packages.fhir.org]https://packages.fhir.org
- [HL7 Package Registry]https://registry.fhir.org