1use crate::cache::models::{
2 CachedApertureSecret, CachedCommand, CachedParameter, CachedRequestBody, CachedSpec,
3};
4use crate::config::models::GlobalConfig;
5use crate::config::url_resolver::BaseUrlResolver;
6use crate::constants;
7use crate::error::Error;
8use crate::spec::resolve_parameter_reference;
9use crate::utils::to_kebab_case;
10use openapiv3::{OpenAPI, Operation, Parameter as OpenApiParameter, ReferenceOr, SecurityScheme};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Serialize, Deserialize)]
17pub struct ApiCapabilityManifest {
18 pub api: ApiInfo,
20 pub commands: HashMap<String, Vec<CommandInfo>>,
22 pub security_schemes: HashMap<String, SecuritySchemeInfo>,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
27pub struct ApiInfo {
28 pub name: String,
30 pub version: String,
32 pub description: Option<String>,
34 pub base_url: String,
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39pub struct CommandInfo {
40 pub name: String,
42 pub method: String,
44 pub path: String,
46 pub description: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub summary: Option<String>,
51 pub operation_id: String,
53 pub parameters: Vec<ParameterInfo>,
55 pub request_body: Option<RequestBodyInfo>,
57 #[serde(skip_serializing_if = "Vec::is_empty", default)]
59 pub security_requirements: Vec<String>,
60 #[serde(skip_serializing_if = "Vec::is_empty", default)]
62 pub tags: Vec<String>,
63 #[serde(skip_serializing_if = "Vec::is_empty", default)]
65 pub original_tags: Vec<String>,
66 #[serde(skip_serializing_if = "std::ops::Not::not", default)]
68 pub deprecated: bool,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub external_docs_url: Option<String>,
72}
73
74#[derive(Debug, Serialize, Deserialize)]
75pub struct ParameterInfo {
76 pub name: String,
78 pub location: String,
80 pub required: bool,
82 pub param_type: String,
84 pub description: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub format: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub default_value: Option<String>,
92 #[serde(skip_serializing_if = "Vec::is_empty", default)]
94 pub enum_values: Vec<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub example: Option<String>,
98}
99
100#[derive(Debug, Serialize, Deserialize)]
101pub struct RequestBodyInfo {
102 pub required: bool,
104 pub content_type: String,
106 pub description: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub example: Option<String>,
111}
112
113#[derive(Debug, Serialize, Deserialize)]
115pub struct SecuritySchemeInfo {
116 #[serde(rename = "type")]
118 pub scheme_type: String,
119 pub description: Option<String>,
121 #[serde(flatten)]
123 pub details: SecuritySchemeDetails,
124 #[serde(rename = "x-aperture-secret", skip_serializing_if = "Option::is_none")]
126 pub aperture_secret: Option<CachedApertureSecret>,
127}
128
129#[derive(Debug, Serialize, Deserialize)]
131#[serde(tag = "scheme", rename_all = "camelCase")]
132pub enum SecuritySchemeDetails {
133 #[serde(rename = "bearer")]
135 HttpBearer {
136 #[serde(skip_serializing_if = "Option::is_none")]
138 bearer_format: Option<String>,
139 },
140 #[serde(rename = "basic")]
142 HttpBasic,
143 #[serde(rename = "apiKey")]
145 ApiKey {
146 #[serde(rename = "in")]
148 location: String,
149 name: String,
151 },
152}
153
154pub fn generate_capability_manifest_from_openapi(
172 api_name: &str,
173 spec: &OpenAPI,
174 global_config: Option<&GlobalConfig>,
175) -> Result<String, Error> {
176 let base_url = spec.servers.first().map(|s| s.url.clone());
178 let servers: Vec<String> = spec.servers.iter().map(|s| s.url.clone()).collect();
179
180 let temp_cached_spec = CachedSpec {
181 cache_format_version: crate::cache::models::CACHE_FORMAT_VERSION,
182 name: api_name.to_string(),
183 version: spec.info.version.clone(),
184 commands: vec![], base_url,
186 servers,
187 security_schemes: HashMap::new(), skipped_endpoints: vec![], server_variables: HashMap::new(), };
191
192 let resolver = BaseUrlResolver::new(&temp_cached_spec);
194 let resolver = if let Some(config) = global_config {
195 resolver.with_global_config(config)
196 } else {
197 resolver
198 };
199 let resolved_base_url = resolver.resolve(None);
200
201 let mut command_groups: HashMap<String, Vec<CommandInfo>> = HashMap::new();
203
204 for (path, path_item) in &spec.paths.paths {
205 if let ReferenceOr::Item(item) = path_item {
206 for (method, operation) in crate::spec::http_methods_iter(item) {
208 if let Some(op) = operation {
209 let command_info = convert_openapi_operation_to_info(
210 method,
211 path,
212 op,
213 spec,
214 spec.security.as_ref(),
215 );
216
217 let group_name = op.tags.first().map_or_else(
219 || constants::DEFAULT_GROUP.to_string(),
220 |tag| to_kebab_case(tag),
221 );
222
223 command_groups
224 .entry(group_name)
225 .or_default()
226 .push(command_info);
227 }
228 }
229 }
230 }
231
232 let security_schemes = extract_security_schemes_from_openapi(spec);
234
235 let manifest = ApiCapabilityManifest {
237 api: ApiInfo {
238 name: spec.info.title.clone(),
239 version: spec.info.version.clone(),
240 description: spec.info.description.clone(),
241 base_url: resolved_base_url,
242 },
243 commands: command_groups,
244 security_schemes,
245 };
246
247 serde_json::to_string_pretty(&manifest)
249 .map_err(|e| Error::serialization_error(format!("Failed to serialize agent manifest: {e}")))
250}
251
252pub fn generate_capability_manifest(
268 spec: &CachedSpec,
269 global_config: Option<&GlobalConfig>,
270) -> Result<String, Error> {
271 let mut command_groups: HashMap<String, Vec<CommandInfo>> = HashMap::new();
272
273 for cached_command in &spec.commands {
275 let group_name = if cached_command.name.is_empty() {
276 constants::DEFAULT_GROUP.to_string()
277 } else {
278 to_kebab_case(&cached_command.name)
279 };
280
281 let command_info = convert_cached_command_to_info(cached_command);
282 command_groups
283 .entry(group_name)
284 .or_default()
285 .push(command_info);
286 }
287
288 let resolver = BaseUrlResolver::new(spec);
290 let resolver = if let Some(config) = global_config {
291 resolver.with_global_config(config)
292 } else {
293 resolver
294 };
295 let base_url = resolver.resolve(None);
296
297 let manifest = ApiCapabilityManifest {
299 api: ApiInfo {
300 name: spec.name.clone(),
301 version: spec.version.clone(),
302 description: None, base_url,
304 },
305 commands: command_groups,
306 security_schemes: extract_security_schemes(spec),
307 };
308
309 serde_json::to_string_pretty(&manifest)
311 .map_err(|e| Error::serialization_error(format!("Failed to serialize agent manifest: {e}")))
312}
313
314fn convert_cached_command_to_info(cached_command: &CachedCommand) -> CommandInfo {
316 let command_name = if cached_command.operation_id.is_empty() {
317 cached_command.method.to_lowercase()
318 } else {
319 to_kebab_case(&cached_command.operation_id)
320 };
321
322 let parameters: Vec<ParameterInfo> = cached_command
323 .parameters
324 .iter()
325 .map(convert_cached_parameter_to_info)
326 .collect();
327
328 let request_body = cached_command
329 .request_body
330 .as_ref()
331 .map(convert_cached_request_body_to_info);
332
333 CommandInfo {
334 name: command_name,
335 method: cached_command.method.clone(),
336 path: cached_command.path.clone(),
337 description: cached_command.description.clone(),
338 summary: cached_command.summary.clone(),
339 operation_id: cached_command.operation_id.clone(),
340 parameters,
341 request_body,
342 security_requirements: cached_command.security_requirements.clone(),
343 tags: cached_command
344 .tags
345 .iter()
346 .map(|t| to_kebab_case(t))
347 .collect(),
348 original_tags: cached_command.tags.clone(),
349 deprecated: cached_command.deprecated,
350 external_docs_url: cached_command.external_docs_url.clone(),
351 }
352}
353
354fn convert_cached_parameter_to_info(cached_param: &CachedParameter) -> ParameterInfo {
356 ParameterInfo {
357 name: cached_param.name.clone(),
358 location: cached_param.location.clone(),
359 required: cached_param.required,
360 param_type: cached_param
361 .schema_type
362 .clone()
363 .unwrap_or_else(|| constants::SCHEMA_TYPE_STRING.to_string()),
364 description: cached_param.description.clone(),
365 format: cached_param.format.clone(),
366 default_value: cached_param.default_value.clone(),
367 enum_values: cached_param.enum_values.clone(),
368 example: cached_param.example.clone(),
369 }
370}
371
372fn convert_cached_request_body_to_info(cached_body: &CachedRequestBody) -> RequestBodyInfo {
374 RequestBodyInfo {
375 required: cached_body.required,
376 content_type: cached_body.content_type.clone(),
377 description: cached_body.description.clone(),
378 example: cached_body.example.clone(),
379 }
380}
381
382fn extract_security_schemes(spec: &CachedSpec) -> HashMap<String, SecuritySchemeInfo> {
384 let mut security_schemes = HashMap::new();
385
386 for (name, scheme) in &spec.security_schemes {
387 let details = match scheme.scheme_type.as_str() {
388 constants::SECURITY_TYPE_HTTP => {
389 scheme.scheme.as_ref().map_or(
390 SecuritySchemeDetails::HttpBearer {
391 bearer_format: None,
392 },
393 |http_scheme| match http_scheme.as_str() {
394 constants::AUTH_SCHEME_BEARER => SecuritySchemeDetails::HttpBearer {
395 bearer_format: scheme.bearer_format.clone(),
396 },
397 constants::AUTH_SCHEME_BASIC => SecuritySchemeDetails::HttpBasic,
398 _ => {
399 SecuritySchemeDetails::HttpBearer {
401 bearer_format: None,
402 }
403 }
404 },
405 )
406 }
407 constants::AUTH_SCHEME_APIKEY => SecuritySchemeDetails::ApiKey {
408 location: scheme
409 .location
410 .clone()
411 .unwrap_or_else(|| constants::LOCATION_HEADER.to_string()),
412 name: scheme
413 .parameter_name
414 .clone()
415 .unwrap_or_else(|| constants::HEADER_AUTHORIZATION.to_string()),
416 },
417 _ => {
418 SecuritySchemeDetails::HttpBearer {
420 bearer_format: None,
421 }
422 }
423 };
424
425 let scheme_info = SecuritySchemeInfo {
426 scheme_type: scheme.scheme_type.clone(),
427 description: scheme.description.clone(),
428 details,
429 aperture_secret: scheme.aperture_secret.clone(),
430 };
431
432 security_schemes.insert(name.clone(), scheme_info);
433 }
434
435 security_schemes
436}
437
438fn convert_openapi_operation_to_info(
440 method: &str,
441 path: &str,
442 operation: &Operation,
443 spec: &OpenAPI,
444 global_security: Option<&Vec<openapiv3::SecurityRequirement>>,
445) -> CommandInfo {
446 let command_name = operation
447 .operation_id
448 .as_ref()
449 .map_or_else(|| method.to_lowercase(), |op_id| to_kebab_case(op_id));
450
451 let parameters: Vec<ParameterInfo> = operation
453 .parameters
454 .iter()
455 .filter_map(|param_ref| match param_ref {
456 ReferenceOr::Item(param) => Some(convert_openapi_parameter_to_info(param)),
457 ReferenceOr::Reference { reference } => resolve_parameter_reference(spec, reference)
458 .ok()
459 .map(|param| convert_openapi_parameter_to_info(¶m)),
460 })
461 .collect();
462
463 let request_body = operation.request_body.as_ref().and_then(|rb_ref| {
465 if let ReferenceOr::Item(body) = rb_ref {
466 let content_type = if body.content.contains_key(constants::CONTENT_TYPE_JSON) {
468 constants::CONTENT_TYPE_JSON
469 } else {
470 body.content.keys().next().map(String::as_str)?
471 };
472
473 let media_type = body.content.get(content_type)?;
474 let example = media_type
475 .example
476 .as_ref()
477 .map(|ex| serde_json::to_string(ex).unwrap_or_else(|_| ex.to_string()));
478
479 Some(RequestBodyInfo {
480 required: body.required,
481 content_type: content_type.to_string(),
482 description: body.description.clone(),
483 example,
484 })
485 } else {
486 None
487 }
488 });
489
490 let security_requirements = operation.security.as_ref().map_or_else(
492 || {
493 global_security.map_or(vec![], |reqs| {
494 reqs.iter().flat_map(|req| req.keys().cloned()).collect()
495 })
496 },
497 |op_security| {
498 op_security
499 .iter()
500 .flat_map(|req| req.keys().cloned())
501 .collect()
502 },
503 );
504
505 CommandInfo {
506 name: command_name,
507 method: method.to_uppercase(),
508 path: path.to_string(),
509 description: operation.description.clone(),
510 summary: operation.summary.clone(),
511 operation_id: operation.operation_id.clone().unwrap_or_default(),
512 parameters,
513 request_body,
514 security_requirements,
515 tags: operation.tags.iter().map(|t| to_kebab_case(t)).collect(),
516 original_tags: operation.tags.clone(),
517 deprecated: operation.deprecated,
518 external_docs_url: operation
519 .external_docs
520 .as_ref()
521 .map(|docs| docs.url.clone()),
522 }
523}
524
525fn convert_openapi_parameter_to_info(param: &OpenApiParameter) -> ParameterInfo {
527 let (param_data, location_str) = match param {
528 OpenApiParameter::Query { parameter_data, .. } => {
529 (parameter_data, constants::PARAM_LOCATION_QUERY)
530 }
531 OpenApiParameter::Header { parameter_data, .. } => {
532 (parameter_data, constants::PARAM_LOCATION_HEADER)
533 }
534 OpenApiParameter::Path { parameter_data, .. } => {
535 (parameter_data, constants::PARAM_LOCATION_PATH)
536 }
537 OpenApiParameter::Cookie { parameter_data, .. } => {
538 (parameter_data, constants::PARAM_LOCATION_COOKIE)
539 }
540 };
541
542 let (schema_type, format, default_value, enum_values, example) =
544 if let openapiv3::ParameterSchemaOrContent::Schema(schema_ref) = ¶m_data.format {
545 match schema_ref {
546 ReferenceOr::Item(schema) => {
547 let (schema_type, format, enums) = match &schema.schema_kind {
548 openapiv3::SchemaKind::Type(type_val) => match type_val {
549 openapiv3::Type::String(string_type) => {
550 let enum_values: Vec<String> = string_type
551 .enumeration
552 .iter()
553 .filter_map(|v| v.as_ref())
554 .map(|v| {
555 serde_json::to_string(v).unwrap_or_else(|_| v.to_string())
556 })
557 .collect();
558 (constants::SCHEMA_TYPE_STRING.to_string(), None, enum_values)
559 }
560 openapiv3::Type::Number(_) => {
561 (constants::SCHEMA_TYPE_NUMBER.to_string(), None, vec![])
562 }
563 openapiv3::Type::Integer(_) => {
564 (constants::SCHEMA_TYPE_INTEGER.to_string(), None, vec![])
565 }
566 openapiv3::Type::Boolean(_) => {
567 (constants::SCHEMA_TYPE_BOOLEAN.to_string(), None, vec![])
568 }
569 openapiv3::Type::Array(_) => {
570 (constants::SCHEMA_TYPE_ARRAY.to_string(), None, vec![])
571 }
572 openapiv3::Type::Object(_) => {
573 (constants::SCHEMA_TYPE_OBJECT.to_string(), None, vec![])
574 }
575 },
576 _ => (constants::SCHEMA_TYPE_STRING.to_string(), None, vec![]),
577 };
578
579 let default_value = schema
580 .schema_data
581 .default
582 .as_ref()
583 .map(|v| serde_json::to_string(v).unwrap_or_else(|_| v.to_string()));
584
585 (Some(schema_type), format, default_value, enums, None)
586 }
587 ReferenceOr::Reference { .. } => (
588 Some(constants::SCHEMA_TYPE_STRING.to_string()),
589 None,
590 None,
591 vec![],
592 None,
593 ),
594 }
595 } else {
596 (
597 Some(constants::SCHEMA_TYPE_STRING.to_string()),
598 None,
599 None,
600 vec![],
601 None,
602 )
603 };
604
605 let example = param_data
607 .example
608 .as_ref()
609 .map(|ex| serde_json::to_string(ex).unwrap_or_else(|_| ex.to_string()))
610 .or(example);
611
612 ParameterInfo {
613 name: param_data.name.clone(),
614 location: location_str.to_string(),
615 required: param_data.required,
616 param_type: schema_type.unwrap_or_else(|| constants::SCHEMA_TYPE_STRING.to_string()),
617 description: param_data.description.clone(),
618 format,
619 default_value,
620 enum_values,
621 example,
622 }
623}
624
625fn extract_security_schemes_from_openapi(spec: &OpenAPI) -> HashMap<String, SecuritySchemeInfo> {
627 let mut security_schemes = HashMap::new();
628
629 if let Some(components) = &spec.components {
630 for (name, scheme_ref) in &components.security_schemes {
631 if let ReferenceOr::Item(scheme) = scheme_ref {
632 if let Some(scheme_info) = convert_openapi_security_scheme(name, scheme) {
633 security_schemes.insert(name.clone(), scheme_info);
634 }
635 }
636 }
637 }
638
639 security_schemes
640}
641
642fn convert_openapi_security_scheme(
644 _name: &str,
645 scheme: &SecurityScheme,
646) -> Option<SecuritySchemeInfo> {
647 match scheme {
648 SecurityScheme::APIKey {
649 location,
650 name: param_name,
651 description,
652 ..
653 } => {
654 let location_str = match location {
655 openapiv3::APIKeyLocation::Query => constants::PARAM_LOCATION_QUERY,
656 openapiv3::APIKeyLocation::Header => constants::PARAM_LOCATION_HEADER,
657 openapiv3::APIKeyLocation::Cookie => constants::PARAM_LOCATION_COOKIE,
658 };
659
660 let aperture_secret = extract_aperture_secret_from_extensions(scheme);
661
662 Some(SecuritySchemeInfo {
663 scheme_type: constants::AUTH_SCHEME_APIKEY.to_string(),
664 description: description.clone(),
665 details: SecuritySchemeDetails::ApiKey {
666 location: location_str.to_string(),
667 name: param_name.clone(),
668 },
669 aperture_secret,
670 })
671 }
672 SecurityScheme::HTTP {
673 scheme: http_scheme,
674 bearer_format,
675 description,
676 ..
677 } => {
678 let details = match http_scheme.as_str() {
679 constants::AUTH_SCHEME_BEARER => SecuritySchemeDetails::HttpBearer {
680 bearer_format: bearer_format.clone(),
681 },
682 constants::AUTH_SCHEME_BASIC => SecuritySchemeDetails::HttpBasic,
683 _ => SecuritySchemeDetails::HttpBearer {
684 bearer_format: None,
685 },
686 };
687
688 let aperture_secret = extract_aperture_secret_from_extensions(scheme);
689
690 Some(SecuritySchemeInfo {
691 scheme_type: constants::SECURITY_TYPE_HTTP.to_string(),
692 description: description.clone(),
693 details,
694 aperture_secret,
695 })
696 }
697 SecurityScheme::OAuth2 { .. } | SecurityScheme::OpenIDConnect { .. } => None,
698 }
699}
700
701fn extract_aperture_secret_from_extensions(
703 scheme: &SecurityScheme,
704) -> Option<CachedApertureSecret> {
705 let extensions = match scheme {
706 SecurityScheme::APIKey { extensions, .. } | SecurityScheme::HTTP { extensions, .. } => {
707 extensions
708 }
709 SecurityScheme::OAuth2 { .. } | SecurityScheme::OpenIDConnect { .. } => return None,
710 };
711
712 extensions
713 .get(constants::EXT_APERTURE_SECRET)
714 .and_then(|value| {
715 if let Some(obj) = value.as_object() {
716 let source = obj.get(constants::EXT_KEY_SOURCE)?.as_str()?;
717 let name = obj.get(constants::EXT_KEY_NAME)?.as_str()?;
718
719 if source == constants::SOURCE_ENV {
720 return Some(CachedApertureSecret {
721 source: source.to_string(),
722 name: name.to_string(),
723 });
724 }
725 }
726 None
727 })
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733 use crate::cache::models::{
734 CachedApertureSecret, CachedCommand, CachedParameter, CachedSecurityScheme, CachedSpec,
735 };
736
737 #[test]
738 fn test_command_name_conversion() {
739 assert_eq!(to_kebab_case("getUserById"), "get-user-by-id");
741 assert_eq!(to_kebab_case("createUser"), "create-user");
742 assert_eq!(to_kebab_case("list"), "list");
743 assert_eq!(to_kebab_case("GET"), "get");
744 assert_eq!(
745 to_kebab_case("List an Organization's Issues"),
746 "list-an-organizations-issues"
747 );
748 }
749
750 #[test]
751 fn test_generate_capability_manifest() {
752 let mut security_schemes = HashMap::new();
753 security_schemes.insert(
754 "bearerAuth".to_string(),
755 CachedSecurityScheme {
756 name: "bearerAuth".to_string(),
757 scheme_type: constants::SECURITY_TYPE_HTTP.to_string(),
758 scheme: Some(constants::AUTH_SCHEME_BEARER.to_string()),
759 location: Some(constants::LOCATION_HEADER.to_string()),
760 parameter_name: Some(constants::HEADER_AUTHORIZATION.to_string()),
761 description: None,
762 bearer_format: None,
763 aperture_secret: Some(CachedApertureSecret {
764 source: constants::SOURCE_ENV.to_string(),
765 name: "API_TOKEN".to_string(),
766 }),
767 },
768 );
769
770 let spec = CachedSpec {
771 cache_format_version: crate::cache::models::CACHE_FORMAT_VERSION,
772 name: "Test API".to_string(),
773 version: "1.0.0".to_string(),
774 commands: vec![CachedCommand {
775 name: "users".to_string(),
776 description: Some("Get user by ID".to_string()),
777 summary: None,
778 operation_id: "getUserById".to_string(),
779 method: constants::HTTP_METHOD_GET.to_string(),
780 path: "/users/{id}".to_string(),
781 parameters: vec![CachedParameter {
782 name: "id".to_string(),
783 location: constants::PARAM_LOCATION_PATH.to_string(),
784 required: true,
785 description: None,
786 schema: Some(constants::SCHEMA_TYPE_STRING.to_string()),
787 schema_type: Some(constants::SCHEMA_TYPE_STRING.to_string()),
788 format: None,
789 default_value: None,
790 enum_values: vec![],
791 example: None,
792 }],
793 request_body: None,
794 responses: vec![],
795 security_requirements: vec!["bearerAuth".to_string()],
796 tags: vec!["users".to_string()],
797 deprecated: false,
798 external_docs_url: None,
799 examples: vec![],
800 }],
801 base_url: Some("https://test-api.example.com".to_string()),
802 servers: vec!["https://test-api.example.com".to_string()],
803 security_schemes,
804 skipped_endpoints: vec![],
805 server_variables: HashMap::new(),
806 };
807
808 let manifest_json = generate_capability_manifest(&spec, None).unwrap();
809 let manifest: ApiCapabilityManifest = serde_json::from_str(&manifest_json).unwrap();
810
811 assert_eq!(manifest.api.name, "Test API");
812 assert_eq!(manifest.api.version, "1.0.0");
813 assert!(manifest.commands.contains_key("users"));
814
815 let users_commands = &manifest.commands["users"];
816 assert_eq!(users_commands.len(), 1);
817 assert_eq!(users_commands[0].name, "get-user-by-id");
818 assert_eq!(users_commands[0].method, constants::HTTP_METHOD_GET);
819 assert_eq!(users_commands[0].parameters.len(), 1);
820 assert_eq!(users_commands[0].parameters[0].name, "id");
821
822 assert!(!manifest.security_schemes.is_empty());
824 assert!(manifest.security_schemes.contains_key("bearerAuth"));
825 let bearer_auth = &manifest.security_schemes["bearerAuth"];
826 assert_eq!(bearer_auth.scheme_type, constants::SECURITY_TYPE_HTTP);
827 assert!(matches!(
828 &bearer_auth.details,
829 SecuritySchemeDetails::HttpBearer { .. }
830 ));
831 assert!(bearer_auth.aperture_secret.is_some());
832 let aperture_secret = bearer_auth.aperture_secret.as_ref().unwrap();
833 assert_eq!(aperture_secret.name, "API_TOKEN");
834 assert_eq!(aperture_secret.source, constants::SOURCE_ENV);
835 }
836}