@ioka-technologies/asyncapi-rust-client-template 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -0
- package/package.json +52 -0
- package/template/Cargo.toml.js +100 -0
- package/template/helpers/index.js +501 -0
- package/template/index.js +244 -0
- package/template/package.json +48 -0
- package/template/src/auth.rs.js +221 -0
- package/template/src/client.rs.js +681 -0
- package/template/src/envelope.rs.js +199 -0
- package/template/src/errors.rs.js +49 -0
- package/template/src/lib.rs.js +112 -0
- package/template/src/models.rs.js +625 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const { File } = require('@asyncapi/generator-react-sdk');
|
|
2
|
+
const React = require('react');
|
|
3
|
+
|
|
4
|
+
module.exports = function ({ asyncapi, params }) {
|
|
5
|
+
// Extract info from AsyncAPI spec
|
|
6
|
+
let title, version, description;
|
|
7
|
+
try {
|
|
8
|
+
const info = asyncapi.info();
|
|
9
|
+
title = info.title();
|
|
10
|
+
version = info.version();
|
|
11
|
+
description = info.description();
|
|
12
|
+
} catch (error) {
|
|
13
|
+
title = 'UnknownAPI';
|
|
14
|
+
version = '1.0.0';
|
|
15
|
+
description = 'Generated NATS client';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Helper function to check if a parameter contains unresolved template variables
|
|
19
|
+
function isTemplateVariable(value) {
|
|
20
|
+
return typeof value === 'string' && value.includes('{{') && value.includes('}}');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Helper function to convert title to kebab-case
|
|
24
|
+
function toKebabCase(str) {
|
|
25
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Helper function to convert title to PascalCase for struct names
|
|
29
|
+
function toPascalCase(str) {
|
|
30
|
+
return str.replace(/[^a-zA-Z0-9]/g, ' ')
|
|
31
|
+
.split(' ')
|
|
32
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
33
|
+
.join('');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Helper function to convert to snake_case for Rust identifiers
|
|
37
|
+
function toSnakeCase(str) {
|
|
38
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Resolve parameters, falling back to extracted values if parameters contain template variables
|
|
42
|
+
const clientName = (params.clientName && !isTemplateVariable(params.clientName))
|
|
43
|
+
? params.clientName
|
|
44
|
+
: `${toPascalCase(title)}Client`;
|
|
45
|
+
|
|
46
|
+
const packageName = (params.packageName && !isTemplateVariable(params.packageName))
|
|
47
|
+
? params.packageName
|
|
48
|
+
: `${toKebabCase(title)}-client`;
|
|
49
|
+
|
|
50
|
+
const packageVersion = (params.packageVersion && !isTemplateVariable(params.packageVersion))
|
|
51
|
+
? params.packageVersion
|
|
52
|
+
: version;
|
|
53
|
+
|
|
54
|
+
const license = (params.license && !isTemplateVariable(params.license))
|
|
55
|
+
? params.license
|
|
56
|
+
: 'Apache-2.0';
|
|
57
|
+
|
|
58
|
+
// Import the source file generators
|
|
59
|
+
const LibRs = require('./src/lib.rs.js');
|
|
60
|
+
const ClientRs = require('./src/client.rs.js');
|
|
61
|
+
const ErrorsRs = require('./src/errors.rs.js');
|
|
62
|
+
const EnvelopeRs = require('./src/envelope.rs.js');
|
|
63
|
+
const ModelsRs = require('./src/models.rs.js');
|
|
64
|
+
const AuthRs = require('./src/auth.rs.js');
|
|
65
|
+
const CargoToml = require('./Cargo.toml.js');
|
|
66
|
+
|
|
67
|
+
// Generate all files from the main index.js
|
|
68
|
+
return [
|
|
69
|
+
// Use the separate Cargo.toml template
|
|
70
|
+
React.createElement(CargoToml, { asyncapi, params }),
|
|
71
|
+
|
|
72
|
+
// Generate Rust source files
|
|
73
|
+
React.createElement(LibRs, { asyncapi, params }),
|
|
74
|
+
React.createElement(ClientRs, { asyncapi, params }),
|
|
75
|
+
React.createElement(ErrorsRs, { asyncapi, params }),
|
|
76
|
+
React.createElement(EnvelopeRs, { asyncapi, params }),
|
|
77
|
+
React.createElement(ModelsRs, { asyncapi, params }),
|
|
78
|
+
React.createElement(AuthRs, { asyncapi, params }),
|
|
79
|
+
|
|
80
|
+
React.createElement(File, { name: 'README.md' },
|
|
81
|
+
`# ${title}
|
|
82
|
+
|
|
83
|
+
${description || 'Generated Rust AsyncAPI NATS client'}
|
|
84
|
+
|
|
85
|
+
## Overview
|
|
86
|
+
|
|
87
|
+
This Rust client provides type-safe access to your AsyncAPI service using NATS messaging. Generated from your AsyncAPI specification, it offers seamless integration with NATS request/reply and pub/sub patterns.
|
|
88
|
+
|
|
89
|
+
## Technical Requirements
|
|
90
|
+
|
|
91
|
+
- Rust 1.70+
|
|
92
|
+
- NATS server
|
|
93
|
+
|
|
94
|
+
## Installation
|
|
95
|
+
|
|
96
|
+
Add this to your \`Cargo.toml\`:
|
|
97
|
+
|
|
98
|
+
\`\`\`toml
|
|
99
|
+
[dependencies]
|
|
100
|
+
${packageName} = "${packageVersion}"
|
|
101
|
+
async-nats = "0.33"
|
|
102
|
+
tokio = { version = "1.0", features = ["full"] }
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
## Quick Start
|
|
106
|
+
|
|
107
|
+
### Basic Usage
|
|
108
|
+
|
|
109
|
+
\`\`\`rust
|
|
110
|
+
use async_nats;
|
|
111
|
+
use ${toSnakeCase(packageName)}::${clientName};
|
|
112
|
+
|
|
113
|
+
#[tokio::main]
|
|
114
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
115
|
+
// Set up your NATS client with desired configuration
|
|
116
|
+
let nats_client = async_nats::connect("nats://localhost:4222").await?;
|
|
117
|
+
|
|
118
|
+
// Create the service client
|
|
119
|
+
let client = ${clientName}::with(nats_client);
|
|
120
|
+
|
|
121
|
+
// Use the generated methods
|
|
122
|
+
// (see generated documentation for specific operations)
|
|
123
|
+
|
|
124
|
+
Ok(())
|
|
125
|
+
}
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
### With Authentication Headers
|
|
129
|
+
|
|
130
|
+
\`\`\`rust
|
|
131
|
+
use async_nats;
|
|
132
|
+
use ${toSnakeCase(packageName)}::{${clientName}, AuthCredentials};
|
|
133
|
+
|
|
134
|
+
#[tokio::main]
|
|
135
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
136
|
+
let nats_client = async_nats::connect("nats://localhost:4222").await?;
|
|
137
|
+
|
|
138
|
+
// Create client with JWT authentication
|
|
139
|
+
let auth = AuthCredentials::jwt("your-jwt-token");
|
|
140
|
+
let client = ${clientName}::with_auth(nats_client, auth)?;
|
|
141
|
+
|
|
142
|
+
// Or with Basic authentication
|
|
143
|
+
// let auth = AuthCredentials::basic("username", "password");
|
|
144
|
+
// let client = ${clientName}::with_auth(nats_client, auth)?;
|
|
145
|
+
|
|
146
|
+
// Or with API Key authentication
|
|
147
|
+
// let auth = AuthCredentials::apikey_header("X-API-Key", "your-api-key");
|
|
148
|
+
// let client = ${clientName}::with_auth(nats_client, auth)?;
|
|
149
|
+
|
|
150
|
+
// All operations will now include authentication headers
|
|
151
|
+
// let result = client.some_operation(payload).await?;
|
|
152
|
+
|
|
153
|
+
Ok(())
|
|
154
|
+
}
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
### With NATS-level Authentication
|
|
158
|
+
|
|
159
|
+
\`\`\`rust
|
|
160
|
+
use async_nats;
|
|
161
|
+
use ${toSnakeCase(packageName)}::${clientName};
|
|
162
|
+
|
|
163
|
+
#[tokio::main]
|
|
164
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
165
|
+
// Set up NATS client with JWT credentials
|
|
166
|
+
let nats_client = async_nats::ConnectOptions::new()
|
|
167
|
+
.credentials_file("./service.creds").await?
|
|
168
|
+
.name("${toKebabCase(title)}-client")
|
|
169
|
+
.connect("nats://server:4222").await?;
|
|
170
|
+
|
|
171
|
+
let client = ${clientName}::with(nats_client);
|
|
172
|
+
|
|
173
|
+
// Use the client...
|
|
174
|
+
|
|
175
|
+
Ok(())
|
|
176
|
+
}
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
### Shared Client Usage
|
|
180
|
+
|
|
181
|
+
\`\`\`rust
|
|
182
|
+
use async_nats;
|
|
183
|
+
use ${toSnakeCase(packageName)}::${clientName};
|
|
184
|
+
|
|
185
|
+
#[tokio::main]
|
|
186
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
187
|
+
// Single NATS client can be shared across multiple service clients
|
|
188
|
+
let nats_client = async_nats::connect("nats://localhost:4222").await?;
|
|
189
|
+
|
|
190
|
+
let client = ${clientName}::with(nats_client.clone());
|
|
191
|
+
// You can create other service clients with the same nats_client
|
|
192
|
+
|
|
193
|
+
Ok(())
|
|
194
|
+
}
|
|
195
|
+
\`\`\`
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
The client accepts any \`async-nats::Client\`, giving you full control over:
|
|
200
|
+
|
|
201
|
+
- **Authentication**: JWT, NKey, username/password, token
|
|
202
|
+
- **TLS**: Custom certificates and encryption
|
|
203
|
+
- **Connection**: Timeouts, retry logic, clustering
|
|
204
|
+
- **Monitoring**: Connection events and health checks
|
|
205
|
+
|
|
206
|
+
See the [async-nats documentation](https://docs.rs/async-nats/) for complete configuration options.
|
|
207
|
+
|
|
208
|
+
## Error Handling
|
|
209
|
+
|
|
210
|
+
The client provides specific error types for different scenarios:
|
|
211
|
+
|
|
212
|
+
\`\`\`rust
|
|
213
|
+
use ${toSnakeCase(packageName)}::{${clientName}, ClientError};
|
|
214
|
+
|
|
215
|
+
match client.some_operation(payload).await {
|
|
216
|
+
Ok(result) => println!("Success: {:?}", result),
|
|
217
|
+
Err(ClientError::Nats(e)) => eprintln!("NATS error: {}", e),
|
|
218
|
+
Err(ClientError::Serialization(e)) => eprintln!("Serialization error: {}", e),
|
|
219
|
+
Err(ClientError::InvalidEnvelope(e)) => eprintln!("Invalid message: {}", e),
|
|
220
|
+
}
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
## Generated from AsyncAPI
|
|
224
|
+
|
|
225
|
+
- **AsyncAPI Version**: ${asyncapi.version()}
|
|
226
|
+
- **Generated**: ${new Date().toISOString()}
|
|
227
|
+
- **Title**: ${title}
|
|
228
|
+
- **Version**: ${packageVersion}
|
|
229
|
+
|
|
230
|
+
## Contributing
|
|
231
|
+
|
|
232
|
+
1. Fork the repository
|
|
233
|
+
2. Create a feature branch
|
|
234
|
+
3. Make your changes and add tests
|
|
235
|
+
4. Run the test suite: \`cargo test\`
|
|
236
|
+
5. Submit a pull request
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
${license}
|
|
241
|
+
`
|
|
242
|
+
)
|
|
243
|
+
];
|
|
244
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asyncapi/rust-client-template",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "AsyncAPI template for generating Rust NATS clients",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"asyncapi",
|
|
8
|
+
"template",
|
|
9
|
+
"rust",
|
|
10
|
+
"nats",
|
|
11
|
+
"client",
|
|
12
|
+
"generator"
|
|
13
|
+
],
|
|
14
|
+
"author": "AsyncAPI Generator",
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@asyncapi/generator-react-sdk": "^1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"generator": {
|
|
20
|
+
"renderer": "react",
|
|
21
|
+
"apiVersion": "v3",
|
|
22
|
+
"supportedProtocols": [
|
|
23
|
+
"nats"
|
|
24
|
+
],
|
|
25
|
+
"parameters": {
|
|
26
|
+
"clientName": {
|
|
27
|
+
"description": "Name of the generated client struct",
|
|
28
|
+
"default": "{{asyncapi.info().title() | replace(/[^a-zA-Z0-9]/g, '') }}Client"
|
|
29
|
+
},
|
|
30
|
+
"packageName": {
|
|
31
|
+
"description": "Name of the generated Rust crate",
|
|
32
|
+
"default": "{{asyncapi.info().title() | kebabCase}}-client"
|
|
33
|
+
},
|
|
34
|
+
"packageVersion": {
|
|
35
|
+
"description": "Version of the generated crate",
|
|
36
|
+
"default": "{{asyncapi.info().version()}}"
|
|
37
|
+
},
|
|
38
|
+
"author": {
|
|
39
|
+
"description": "Author of the generated crate",
|
|
40
|
+
"default": "AsyncAPI Generator"
|
|
41
|
+
},
|
|
42
|
+
"license": {
|
|
43
|
+
"description": "License of the generated crate",
|
|
44
|
+
"default": "Apache-2.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import { File } from '@asyncapi/generator-react-sdk';
|
|
3
|
+
|
|
4
|
+
module.exports = function ({ asyncapi, params }) {
|
|
5
|
+
return (
|
|
6
|
+
<File name="auth.rs">
|
|
7
|
+
{`//! Authentication support for the NATS client
|
|
8
|
+
|
|
9
|
+
use base64::Engine;
|
|
10
|
+
use serde::{Deserialize, Serialize};
|
|
11
|
+
use std::collections::HashMap;
|
|
12
|
+
use thiserror::Error;
|
|
13
|
+
|
|
14
|
+
/// Authentication credentials for different auth types
|
|
15
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
16
|
+
pub struct AuthCredentials {
|
|
17
|
+
/// JWT Bearer token
|
|
18
|
+
pub jwt: Option<String>,
|
|
19
|
+
/// Basic authentication credentials
|
|
20
|
+
pub basic: Option<BasicAuth>,
|
|
21
|
+
/// API Key authentication
|
|
22
|
+
pub apikey: Option<ApiKeyAuth>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Basic authentication credentials
|
|
26
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
27
|
+
pub struct BasicAuth {
|
|
28
|
+
pub username: String,
|
|
29
|
+
pub password: String,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// API Key authentication configuration
|
|
33
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
34
|
+
pub struct ApiKeyAuth {
|
|
35
|
+
pub key: String,
|
|
36
|
+
pub name: String,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Auth-related error types
|
|
40
|
+
#[derive(Debug, Error)]
|
|
41
|
+
pub enum AuthError {
|
|
42
|
+
#[error("JWT token must be a non-empty string")]
|
|
43
|
+
InvalidJwt,
|
|
44
|
+
|
|
45
|
+
#[error("Basic auth requires both username and password")]
|
|
46
|
+
InvalidBasicAuth,
|
|
47
|
+
|
|
48
|
+
#[error("API key auth requires both key and name")]
|
|
49
|
+
InvalidApiKey,
|
|
50
|
+
|
|
51
|
+
#[error("Token has expired")]
|
|
52
|
+
TokenExpired,
|
|
53
|
+
|
|
54
|
+
#[error("Unauthorized access")]
|
|
55
|
+
Unauthorized,
|
|
56
|
+
|
|
57
|
+
#[error("Authentication failed: {0}")]
|
|
58
|
+
AuthenticationFailed(String),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl AuthCredentials {
|
|
62
|
+
/// Create new JWT credentials
|
|
63
|
+
pub fn jwt(token: impl Into<String>) -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
jwt: Some(token.into()),
|
|
66
|
+
basic: None,
|
|
67
|
+
apikey: None,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Create new Basic auth credentials
|
|
72
|
+
pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
|
|
73
|
+
Self {
|
|
74
|
+
jwt: None,
|
|
75
|
+
basic: Some(BasicAuth {
|
|
76
|
+
username: username.into(),
|
|
77
|
+
password: password.into(),
|
|
78
|
+
}),
|
|
79
|
+
apikey: None,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Create new API key credentials for header
|
|
84
|
+
pub fn apikey_header(name: impl Into<String>, key: impl Into<String>) -> Self {
|
|
85
|
+
Self {
|
|
86
|
+
jwt: None,
|
|
87
|
+
basic: None,
|
|
88
|
+
apikey: Some(ApiKeyAuth {
|
|
89
|
+
key: key.into(),
|
|
90
|
+
name: name.into(),
|
|
91
|
+
}),
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Check if any credentials are provided
|
|
96
|
+
pub fn has_credentials(&self) -> bool {
|
|
97
|
+
self.jwt.is_some() || self.basic.is_some() || self.apikey.is_some()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Get the auth type as a string
|
|
101
|
+
pub fn auth_type(&self) -> Option<&'static str> {
|
|
102
|
+
if self.jwt.is_some() {
|
|
103
|
+
Some("jwt")
|
|
104
|
+
} else if self.basic.is_some() {
|
|
105
|
+
Some("basic")
|
|
106
|
+
} else if self.apikey.is_some() {
|
|
107
|
+
Some("apikey")
|
|
108
|
+
} else {
|
|
109
|
+
None
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Validate the credentials
|
|
114
|
+
pub fn validate(&self) -> Result<(), AuthError> {
|
|
115
|
+
if let Some(ref jwt) = self.jwt {
|
|
116
|
+
if jwt.trim().is_empty() {
|
|
117
|
+
return Err(AuthError::InvalidJwt);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if let Some(ref basic) = self.basic {
|
|
122
|
+
if basic.username.is_empty() || basic.password.is_empty() {
|
|
123
|
+
return Err(AuthError::InvalidBasicAuth);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if let Some(ref apikey) = self.apikey {
|
|
128
|
+
if apikey.key.is_empty() || apikey.name.is_empty() {
|
|
129
|
+
return Err(AuthError::InvalidApiKey);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Ok(())
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Generate authentication headers based on credentials
|
|
138
|
+
pub fn generate_auth_headers(auth: &AuthCredentials) -> HashMap<String, String> {
|
|
139
|
+
let mut headers = HashMap::new();
|
|
140
|
+
|
|
141
|
+
if let Some(ref jwt) = auth.jwt {
|
|
142
|
+
headers.insert("Authorization".to_string(), format!("Bearer {}", jwt));
|
|
143
|
+
} else if let Some(ref basic) = auth.basic {
|
|
144
|
+
let credentials = base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", basic.username, basic.password));
|
|
145
|
+
headers.insert("Authorization".to_string(), format!("Basic {}", credentials));
|
|
146
|
+
} else if let Some(ref apikey) = auth.apikey {
|
|
147
|
+
headers.insert(apikey.name.clone(), apikey.key.clone());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
headers
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[cfg(test)]
|
|
154
|
+
mod tests {
|
|
155
|
+
use super::*;
|
|
156
|
+
|
|
157
|
+
#[test]
|
|
158
|
+
fn test_jwt_credentials() {
|
|
159
|
+
let auth = AuthCredentials::jwt("test-token");
|
|
160
|
+
assert!(auth.has_credentials());
|
|
161
|
+
assert_eq!(auth.auth_type(), Some("jwt"));
|
|
162
|
+
assert!(auth.validate().is_ok());
|
|
163
|
+
|
|
164
|
+
let headers = generate_auth_headers(&auth);
|
|
165
|
+
assert_eq!(headers.get("Authorization"), Some(&"Bearer test-token".to_string()));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn test_basic_credentials() {
|
|
170
|
+
let auth = AuthCredentials::basic("user", "pass");
|
|
171
|
+
assert!(auth.has_credentials());
|
|
172
|
+
assert_eq!(auth.auth_type(), Some("basic"));
|
|
173
|
+
assert!(auth.validate().is_ok());
|
|
174
|
+
|
|
175
|
+
let headers = generate_auth_headers(&auth);
|
|
176
|
+
let expected = format!("Basic {}", base64::engine::general_purpose::STANDARD.encode("user:pass"));
|
|
177
|
+
assert_eq!(headers.get("Authorization"), Some(&expected));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_apikey_header_credentials() {
|
|
182
|
+
let auth = AuthCredentials::apikey_header("X-API-Key", "secret");
|
|
183
|
+
assert!(auth.has_credentials());
|
|
184
|
+
assert_eq!(auth.auth_type(), Some("apikey"));
|
|
185
|
+
assert!(auth.validate().is_ok());
|
|
186
|
+
|
|
187
|
+
let headers = generate_auth_headers(&auth);
|
|
188
|
+
assert_eq!(headers.get("X-API-Key"), Some(&"secret".to_string()));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn test_empty_credentials() {
|
|
193
|
+
let auth = AuthCredentials {
|
|
194
|
+
jwt: None,
|
|
195
|
+
basic: None,
|
|
196
|
+
apikey: None,
|
|
197
|
+
};
|
|
198
|
+
assert!(!auth.has_credentials());
|
|
199
|
+
assert_eq!(auth.auth_type(), None);
|
|
200
|
+
assert!(auth.validate().is_ok());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn test_invalid_jwt() {
|
|
205
|
+
let auth = AuthCredentials::jwt("");
|
|
206
|
+
assert!(auth.validate().is_err());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[test]
|
|
210
|
+
fn test_invalid_basic() {
|
|
211
|
+
let auth = AuthCredentials::basic("", "pass");
|
|
212
|
+
assert!(auth.validate().is_err());
|
|
213
|
+
|
|
214
|
+
let auth = AuthCredentials::basic("user", "");
|
|
215
|
+
assert!(auth.validate().is_err());
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`}
|
|
219
|
+
</File>
|
|
220
|
+
);
|
|
221
|
+
};
|