1use crate::{GeneratorError, generator::GeneratorConfig};
169use serde::{Deserialize, Serialize};
170use std::collections::BTreeMap;
171use std::path::{Path, PathBuf};
172
173#[derive(Debug, Clone)]
175pub struct ConfigFile {
176 pub generator: GeneratorSection,
177 pub features: FeaturesSection,
178 pub http_client: Option<HttpClientSection>,
179 pub streaming: Option<StreamingSection>,
180 pub server: Option<ServerSection>,
183 pub client: Option<ClientSection>,
185 pub nullable_overrides: BTreeMap<String, bool>,
186 pub extensible_enums: BTreeMap<String, bool>,
191 pub type_mappings: BTreeMap<String, String>,
192 pub types: crate::type_mapping::TypeMappingConfig,
198}
199
200#[derive(Debug, Clone, Deserialize, Serialize)]
201#[serde(deny_unknown_fields)]
202pub struct GeneratorSection {
203 pub spec_path: PathBuf,
206 pub output_dir: PathBuf,
209 pub module_name: String,
216 #[serde(default)]
219 pub schema_extensions: Vec<PathBuf>,
220 #[serde(default)]
222 pub overlays: Vec<PathBuf>,
223 #[serde(default)]
225 pub effective_spec: Option<PathBuf>,
226 #[serde(default)]
228 pub bindings_metadata: Option<String>,
229 #[serde(default)]
231 pub builders: BuildersSection,
232 #[serde(default)]
234 pub derives: crate::derives::DerivesConfig,
235}
236
237#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
239#[serde(default, deny_unknown_fields)]
240pub struct BuildersSection {
241 pub enabled: bool,
243 pub threshold: usize,
246}
247
248impl Default for BuildersSection {
249 fn default() -> Self {
250 Self {
251 enabled: false,
252 threshold: 3,
253 }
254 }
255}
256
257#[derive(Debug, Clone, Deserialize, Serialize)]
258#[serde(deny_unknown_fields)]
259pub struct FeaturesSection {
260 #[serde(default)]
261 pub enable_sse_client: bool,
262 #[serde(default)]
263 pub enable_async_client: bool,
264 #[serde(default)]
265 pub enable_specta: bool,
266 #[serde(default)]
268 pub enable_registry: bool,
269 #[serde(default)]
271 pub registry_only: bool,
272}
273
274#[derive(Debug, Clone, Deserialize, Serialize)]
282#[serde(deny_unknown_fields)]
283pub struct ServerSection {
284 pub framework: String,
286 #[serde(default)]
289 pub operations: Vec<String>,
290 #[serde(default)]
294 pub prune_models: bool,
295 #[serde(default)]
297 pub validation: ServerValidationSection,
298}
299
300#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
302#[serde(default, deny_unknown_fields)]
303pub struct ServerValidationSection {
304 pub enabled: bool,
306 pub max_body_bytes: usize,
308 pub max_errors: usize,
310}
311
312impl Default for ServerValidationSection {
313 fn default() -> Self {
314 Self {
315 enabled: true,
316 max_body_bytes: 2_097_152,
317 max_errors: 16,
318 }
319 }
320}
321
322#[derive(Debug, Clone, Deserialize, Serialize)]
331#[serde(deny_unknown_fields)]
332pub struct ClientSection {
333 #[serde(default)]
336 pub operations: Vec<String>,
337 #[serde(default)]
340 pub prune_models: bool,
341}
342
343impl ClientSection {
344 pub fn parsed_selectors(
346 &self,
347 ) -> Result<Vec<crate::server::Selector>, crate::server::SelectorParseError> {
348 self.operations
349 .iter()
350 .map(|s| crate::server::Selector::parse(s))
351 .collect()
352 }
353}
354
355impl ServerSection {
356 pub fn parsed_selectors(
359 &self,
360 ) -> Result<Vec<crate::server::Selector>, crate::server::SelectorParseError> {
361 self.operations
362 .iter()
363 .map(|s| crate::server::Selector::parse(s))
364 .collect()
365 }
366}
367
368#[derive(Debug, Clone, Deserialize, Serialize)]
369#[serde(deny_unknown_fields)]
370pub struct HttpClientSection {
371 pub base_url: Option<String>,
372 pub timeout_seconds: Option<u64>,
373 pub max_response_body_bytes: Option<usize>,
374 pub auth: Option<AuthConfigSection>,
375 #[serde(default)]
376 pub headers: Vec<HeaderEntry>,
377 pub retry: Option<RetryConfigSection>,
378 pub tracing: Option<TracingConfigSection>,
379}
380
381#[derive(Debug, Clone, Deserialize, Serialize)]
382#[serde(deny_unknown_fields)]
383pub struct TracingConfigSection {
384 #[serde(default = "default_tracing_enabled")]
385 pub enabled: bool,
386}
387
388fn default_tracing_enabled() -> bool {
389 true
390}
391
392#[derive(Debug, Clone, Deserialize, Serialize)]
393#[serde(deny_unknown_fields)]
394pub struct RetryConfigSection {
395 #[serde(default = "default_max_retries")]
396 pub max_retries: u32,
397 #[serde(default = "default_initial_delay_ms")]
398 pub initial_delay_ms: u64,
399 #[serde(default = "default_max_delay_ms")]
400 pub max_delay_ms: u64,
401}
402
403fn default_max_retries() -> u32 {
404 3
405}
406fn default_initial_delay_ms() -> u64 {
407 500
408}
409fn default_max_delay_ms() -> u64 {
410 16000
411}
412
413#[derive(Debug, Clone, Deserialize, Serialize)]
414#[serde(deny_unknown_fields)]
415pub struct AuthConfigSection {
416 #[serde(rename = "type")]
417 pub auth_type: String,
418 pub header_name: String,
419}
420
421#[derive(Debug, Clone, Deserialize, Serialize)]
422#[serde(deny_unknown_fields)]
423pub struct HeaderEntry {
424 pub name: String,
425 pub value: String,
426}
427
428#[derive(Debug, Clone, Deserialize, Serialize)]
429#[serde(deny_unknown_fields)]
430pub struct StreamingSection {
431 pub endpoints: Vec<StreamingEndpointSection>,
432}
433
434#[derive(Debug, Clone, Deserialize, Serialize)]
435#[serde(deny_unknown_fields)]
436pub struct StreamingEndpointSection {
437 pub operation_id: String,
438 pub path: String,
439 #[serde(default)]
441 pub http_method: Option<String>,
442 #[serde(default)]
444 pub stream_parameter: String,
445 #[serde(default)]
447 pub query_parameters: Vec<QueryParameterSection>,
448 pub event_union_type: String,
449 pub content_type: Option<String>,
450 pub event_flow: Option<EventFlowSection>,
451}
452
453#[derive(Debug, Clone, Deserialize, Serialize)]
454#[serde(deny_unknown_fields)]
455pub struct QueryParameterSection {
456 pub name: String,
457 #[serde(default)]
458 pub required: bool,
459}
460
461#[derive(Debug, Clone, Deserialize, Serialize)]
462#[serde(deny_unknown_fields)]
463pub struct EventFlowSection {
464 #[serde(rename = "type")]
465 pub flow_type: String,
466 pub start_events: Option<Vec<String>>,
467 pub delta_events: Option<Vec<String>>,
468 pub stop_events: Option<Vec<String>>,
469}
470
471#[derive(Deserialize)]
475#[serde(deny_unknown_fields)]
476struct ConfigFileWire {
477 generator: GeneratorSectionWire,
478 features: FeaturesSection,
479 #[serde(default)]
480 http_client: Option<HttpClientSection>,
481 #[serde(default)]
482 streaming: Option<StreamingSection>,
483 #[serde(default)]
484 server: Option<ServerSection>,
485 #[serde(default)]
486 client: Option<ClientSection>,
487 #[serde(default)]
488 nullable_overrides: BTreeMap<String, bool>,
489 #[serde(default)]
490 extensible_enums: BTreeMap<String, bool>,
491 #[serde(default)]
492 type_mappings: BTreeMap<String, String>,
493 #[serde(default)]
494 types: Option<crate::type_mapping::TypeMappingConfig>,
495}
496
497#[derive(Deserialize)]
498#[serde(deny_unknown_fields)]
499struct GeneratorSectionWire {
500 spec_path: PathBuf,
501 output_dir: PathBuf,
502 module_name: String,
503 #[serde(default)]
504 schema_extensions: Vec<PathBuf>,
505 #[serde(default)]
506 overlays: Vec<PathBuf>,
507 #[serde(default)]
508 effective_spec: Option<PathBuf>,
509 #[serde(default)]
510 bindings_metadata: Option<String>,
511 #[serde(default)]
512 builders: BuildersSection,
513 #[serde(default)]
514 derives: crate::derives::DerivesConfig,
515 #[serde(default)]
516 types: Option<crate::type_mapping::TypeMappingConfig>,
517}
518
519impl TryFrom<ConfigFileWire> for ConfigFile {
520 type Error = String;
521
522 fn try_from(wire: ConfigFileWire) -> Result<Self, Self::Error> {
523 wire.generator
524 .derives
525 .validate_paths()
526 .map_err(|error| error.to_string())?;
527 let types = match (wire.generator.types, wire.types) {
528 (Some(_), Some(_)) => {
529 return Err(
530 "Configuration contains both legacy [types] and canonical [generator.types]. Remove [types] and keep [generator.types]."
531 .to_string(),
532 );
533 }
534 (Some(types), None) | (None, Some(types)) => types,
535 (None, None) => crate::type_mapping::TypeMappingConfig::default(),
536 };
537
538 Ok(Self {
539 generator: GeneratorSection {
540 spec_path: wire.generator.spec_path,
541 output_dir: wire.generator.output_dir,
542 module_name: wire.generator.module_name,
543 schema_extensions: wire.generator.schema_extensions,
544 overlays: wire.generator.overlays,
545 effective_spec: wire.generator.effective_spec,
546 bindings_metadata: wire.generator.bindings_metadata,
547 builders: wire.generator.builders,
548 derives: wire.generator.derives,
549 },
550 features: wire.features,
551 http_client: wire.http_client,
552 streaming: wire.streaming,
553 server: wire.server,
554 client: wire.client,
555 nullable_overrides: wire.nullable_overrides,
556 extensible_enums: wire.extensible_enums,
557 type_mappings: wire.type_mappings,
558 types,
559 })
560 }
561}
562
563impl<'de> Deserialize<'de> for ConfigFile {
564 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
565 where
566 D: serde::Deserializer<'de>,
567 {
568 ConfigFileWire::deserialize(deserializer)?
569 .try_into()
570 .map_err(serde::de::Error::custom)
571 }
572}
573
574#[derive(Serialize)]
575struct ConfigFileRef<'a> {
576 generator: GeneratorSectionRef<'a>,
577 features: &'a FeaturesSection,
578 #[serde(skip_serializing_if = "Option::is_none")]
579 http_client: Option<&'a HttpClientSection>,
580 #[serde(skip_serializing_if = "Option::is_none")]
581 streaming: Option<&'a StreamingSection>,
582 #[serde(skip_serializing_if = "Option::is_none")]
583 server: Option<&'a ServerSection>,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 client: Option<&'a ClientSection>,
586 nullable_overrides: &'a BTreeMap<String, bool>,
587 extensible_enums: &'a BTreeMap<String, bool>,
588 type_mappings: &'a BTreeMap<String, String>,
589}
590
591#[derive(Serialize)]
592struct GeneratorSectionRef<'a> {
593 spec_path: &'a Path,
594 output_dir: &'a Path,
595 module_name: &'a str,
596 schema_extensions: &'a [PathBuf],
597 overlays: &'a [PathBuf],
598 #[serde(skip_serializing_if = "Option::is_none")]
599 effective_spec: Option<&'a Path>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 bindings_metadata: Option<&'a str>,
602 builders: &'a BuildersSection,
603 derives: &'a crate::derives::DerivesConfig,
604 types: &'a crate::type_mapping::TypeMappingConfig,
605}
606
607impl Serialize for ConfigFile {
608 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
609 where
610 S: serde::Serializer,
611 {
612 ConfigFileRef {
613 generator: GeneratorSectionRef {
614 spec_path: &self.generator.spec_path,
615 output_dir: &self.generator.output_dir,
616 module_name: &self.generator.module_name,
617 schema_extensions: &self.generator.schema_extensions,
618 overlays: &self.generator.overlays,
619 effective_spec: self.generator.effective_spec.as_deref(),
620 bindings_metadata: self.generator.bindings_metadata.as_deref(),
621 builders: &self.generator.builders,
622 derives: &self.generator.derives,
623 types: &self.types,
624 },
625 features: &self.features,
626 http_client: self.http_client.as_ref(),
627 streaming: self.streaming.as_ref(),
628 server: self.server.as_ref(),
629 client: self.client.as_ref(),
630 nullable_overrides: &self.nullable_overrides,
631 extensible_enums: &self.extensible_enums,
632 type_mappings: &self.type_mappings,
633 }
634 .serialize(serializer)
635 }
636}
637
638fn resolve_relative_path(config_dir: &Path, path: &mut PathBuf) {
639 if path.is_relative() && !path.to_string_lossy().contains("://") {
640 *path = config_dir.join(&*path);
641 }
642}
643
644fn inspect_type_config_layout(value: &toml::Value) -> Result<(), GeneratorError> {
645 let legacy_types = value.get("types").is_some();
646 let canonical_types = value
647 .get("generator")
648 .and_then(|generator| generator.get("types"));
649
650 if legacy_types && canonical_types.is_some() {
651 return Err(GeneratorError::ValidationError(
652 "Configuration contains both legacy [types] and canonical [generator.types]. Remove [types] and keep [generator.types]."
653 .to_string(),
654 ));
655 }
656
657 if canonical_types
658 .and_then(|types| types.get("strategies"))
659 .is_some()
660 {
661 return Err(GeneratorError::ValidationError(
662 "[generator.types.strategies] is obsolete. Move its fields directly under [generator.types]. Use snake_case keys such as date_time (not date-time); valid byte values are string, base64, base64_url_unpadded, and vec_u8 (for example: byte = \"base64\")."
663 .to_string(),
664 ));
665 }
666
667 Ok(())
668}
669
670impl ConfigFile {
671 pub fn load(path: &Path) -> Result<Self, GeneratorError> {
677 let config_path = path.canonicalize().map_err(|e| GeneratorError::FileError {
678 message: format!("Failed to resolve config file '{}': {}", path.display(), e),
679 })?;
680 let config_dir = config_path
681 .parent()
682 .ok_or_else(|| GeneratorError::FileError {
683 message: format!(
684 "Config file '{}' has no parent directory",
685 config_path.display()
686 ),
687 })?;
688 let content =
689 std::fs::read_to_string(&config_path).map_err(|e| GeneratorError::FileError {
690 message: format!(
691 "Failed to read config file '{}': {}",
692 config_path.display(),
693 e
694 ),
695 })?;
696
697 let value: toml::Value =
698 toml::from_str(&content).map_err(|e| GeneratorError::FileError {
699 message: format!(
700 "Failed to parse TOML config: {}\n\nExample config:\n{}",
701 e, EXAMPLE_CONFIG
702 ),
703 })?;
704 inspect_type_config_layout(&value)?;
705
706 let mut config: ConfigFile =
707 toml::from_str(&content).map_err(|e| GeneratorError::FileError {
708 message: format!(
709 "Failed to parse TOML config: {}\n\nExample config:\n{}",
710 e, EXAMPLE_CONFIG
711 ),
712 })?;
713
714 resolve_relative_path(config_dir, &mut config.generator.spec_path);
715 resolve_relative_path(config_dir, &mut config.generator.output_dir);
716 for extension in &mut config.generator.schema_extensions {
717 resolve_relative_path(config_dir, extension);
718 }
719
720 for overlay in &mut config.generator.overlays {
721 resolve_relative_path(config_dir, overlay);
722 }
723
724 config.validate()?;
725
726 Ok(config)
727 }
728
729 fn validate(&self) -> Result<(), GeneratorError> {
730 let mut errors = Vec::new();
731 for (field, path) in [
732 ("effective_spec", self.generator.effective_spec.as_deref()),
733 (
734 "bindings_metadata",
735 self.generator.bindings_metadata.as_deref().map(Path::new),
736 ),
737 ] {
738 if let Some(path) = path {
739 if let Err(error) = crate::overlay::validate_artifact_path(path) {
740 errors.push(format!("generator.{field}: {error}"));
741 }
742 }
743 }
744 for path in &self.generator.overlays {
745 if !path.is_file() {
746 errors.push(format!(
747 "generator.overlays: Overlay file not found: {}",
748 path.display()
749 ));
750 }
751 }
752
753 let spec_source = self.generator.spec_path.to_string_lossy();
754 if crate::spec_source::is_remote_spec(&spec_source) {
755 if let Err(error) = crate::spec_source::validate_remote_spec_url(&spec_source) {
756 errors.push(format!("generator.spec_path: {error}"));
757 }
758 } else if spec_source.contains("://") {
759 let error = crate::spec_source::validate_remote_spec_url(&spec_source)
760 .err()
761 .unwrap_or_else(|| "unsupported remote OpenAPI URL".to_string());
762 errors.push(format!("generator.spec_path: {error}"));
763 } else if !self.generator.spec_path.exists() {
764 errors.push(format!(
765 "generator.spec_path: OpenAPI spec file not found: {}. Ensure spec_path points to a valid OpenAPI JSON or YAML file.",
766 self.generator.spec_path.display()
767 ));
768 }
769 if self.generator.module_name.is_empty() {
770 errors.push("generator.module_name: module_name cannot be empty".to_string());
771 }
772
773 if let Some(server) = &self.server
774 && server.framework != "axum"
775 {
776 errors.push(format!(
777 "server.framework: framework must be \"axum\" (got \"{}\"); other frameworks are not supported yet",
778 server.framework
779 ));
780 }
781
782 if let Some(client) = &self.client {
783 for (index, selector) in client.operations.iter().enumerate() {
784 if let Err(error) = crate::server::Selector::parse(selector) {
785 errors.push(format!("client.operations[{index}]: {error}"));
786 }
787 }
788 }
789 if let Some(server) = &self.server {
790 if !(1..=67_108_864).contains(&server.validation.max_body_bytes) {
791 errors.push(
792 "server.validation.max_body_bytes: max_body_bytes must be between 1 and 67108864"
793 .to_string(),
794 );
795 }
796 if !(1..=100).contains(&server.validation.max_errors) {
797 errors.push(
798 "server.validation.max_errors: max_errors must be between 1 and 100"
799 .to_string(),
800 );
801 }
802 for (index, selector) in server.operations.iter().enumerate() {
803 if let Err(error) = crate::server::Selector::parse(selector) {
804 errors.push(format!("server.operations[{index}]: {error}"));
805 }
806 }
807 }
808
809 if let Some(http) = &self.http_client {
810 if let Some(base_url) = &http.base_url
811 && url::Url::parse(base_url).is_err()
812 {
813 errors.push("http_client.base_url: base_url must be a valid URL".to_string());
814 }
815 if let Some(timeout) = http.timeout_seconds
816 && !(1..=3600).contains(&timeout)
817 {
818 errors.push(
819 "http_client.timeout_seconds: timeout_seconds must be between 1 and 3600"
820 .to_string(),
821 );
822 }
823 if let Some(auth) = &http.auth {
824 if !matches!(auth.auth_type.as_str(), "Bearer" | "ApiKey" | "Custom") {
825 errors.push(format!(
826 "http_client.auth.type: Invalid auth type '{}'. Must be one of: Bearer, ApiKey, Custom",
827 auth.auth_type
828 ));
829 }
830 if auth.header_name.is_empty() {
831 errors.push(
832 "http_client.auth.header_name: header_name cannot be empty".to_string(),
833 );
834 }
835 }
836 for (index, header) in http.headers.iter().enumerate() {
837 if header.name.is_empty() {
838 errors.push(format!(
839 "http_client.headers[{index}].name: header name cannot be empty"
840 ));
841 }
842 }
843 if let Some(retry) = &http.retry {
844 if retry.max_retries > 10 {
845 errors.push(
846 "http_client.retry.max_retries: max_retries must be between 0 and 10"
847 .to_string(),
848 );
849 }
850 if !(100..=10000).contains(&retry.initial_delay_ms) {
851 errors.push(
852 "http_client.retry.initial_delay_ms: initial_delay_ms must be between 100 and 10000"
853 .to_string(),
854 );
855 }
856 if !(1000..=300000).contains(&retry.max_delay_ms) {
857 errors.push(
858 "http_client.retry.max_delay_ms: max_delay_ms must be between 1000 and 300000"
859 .to_string(),
860 );
861 }
862 }
863 }
864
865 if let Some(streaming) = &self.streaming {
866 for (index, endpoint) in streaming.endpoints.iter().enumerate() {
867 let prefix = format!("streaming.endpoints[{index}]");
868 if endpoint.operation_id.is_empty() {
869 errors.push(format!("{prefix}.operation_id: must not be empty"));
870 }
871 if endpoint.path.is_empty() {
872 errors.push(format!("{prefix}.path: must not be empty"));
873 }
874 if endpoint.event_union_type.is_empty() {
875 errors.push(format!("{prefix}.event_union_type: must not be empty"));
876 }
877 for (query_index, query) in endpoint.query_parameters.iter().enumerate() {
878 if query.name.is_empty() {
879 errors.push(format!(
880 "{prefix}.query_parameters[{query_index}].name: must not be empty"
881 ));
882 }
883 }
884 if let Some(flow) = &endpoint.event_flow
885 && !matches!(
886 flow.flow_type.as_str(),
887 "StartDeltaStop" | "start_delta_stop" | "Continuous"
888 )
889 {
890 errors.push(format!(
891 "{prefix}.event_flow.type: Invalid event flow type '{}'. Must be one of: StartDeltaStop, Continuous",
892 flow.flow_type
893 ));
894 }
895 }
896 }
897
898 if errors.is_empty() {
899 Ok(())
900 } else {
901 Err(GeneratorError::ValidationError(format!(
902 "Configuration validation failed:\n - {}",
903 errors.join("\n - ")
904 )))
905 }
906 }
907
908 pub fn into_generator_config(self) -> GeneratorConfig {
910 use crate::http_config::{AuthConfig, HttpClientConfig, RetryConfig};
911
912 let types = self.types;
913
914 let http_client_config = self.http_client.as_ref().map(|http| HttpClientConfig {
916 base_url: http.base_url.clone(),
917 timeout_seconds: http.timeout_seconds,
918 max_response_body_bytes: http.max_response_body_bytes,
919 default_headers: http
920 .headers
921 .iter()
922 .map(|h| (h.name.clone(), h.value.clone()))
923 .collect(),
924 });
925
926 let retry_config = self
928 .http_client
929 .as_ref()
930 .and_then(|http| http.retry.as_ref())
931 .map(|retry| RetryConfig {
932 max_retries: retry.max_retries,
933 initial_delay_ms: retry.initial_delay_ms,
934 max_delay_ms: retry.max_delay_ms,
935 });
936
937 let tracing_enabled = self
939 .http_client
940 .as_ref()
941 .and_then(|http| http.tracing.as_ref())
942 .map(|tracing| tracing.enabled)
943 .unwrap_or(true);
944
945 let auth_config = self
947 .http_client
948 .as_ref()
949 .and_then(|http| http.auth.as_ref())
950 .map(|auth| match auth.auth_type.as_str() {
951 "Bearer" => AuthConfig::Bearer {
952 header_name: auth.header_name.clone(),
953 },
954 "ApiKey" => AuthConfig::ApiKey {
955 header_name: auth.header_name.clone(),
956 },
957 "Custom" => AuthConfig::Custom {
958 header_name: auth.header_name.clone(),
959 header_value_prefix: None,
960 },
961 _ => AuthConfig::Bearer {
962 header_name: "Authorization".to_string(),
963 },
964 });
965
966 let streaming_config = self.streaming.map(|section| {
968 use crate::streaming::{
969 EventFlow, HttpMethod, QueryParameter, StreamingConfig, StreamingEndpoint,
970 };
971
972 let endpoints = section
973 .endpoints
974 .into_iter()
975 .map(|e| {
976 let event_flow = e
977 .event_flow
978 .map(|ef| match ef.flow_type.as_str() {
979 "StartDeltaStop" | "start_delta_stop" => EventFlow::StartDeltaStop {
980 start_events: ef.start_events.unwrap_or_default(),
981 delta_events: ef.delta_events.unwrap_or_default(),
982 stop_events: ef.stop_events.unwrap_or_default(),
983 },
984 _ => EventFlow::Simple,
985 })
986 .unwrap_or(EventFlow::Simple);
987
988 let http_method = e
989 .http_method
990 .map(|m| match m.to_uppercase().as_str() {
991 "GET" => HttpMethod::Get,
992 _ => HttpMethod::Post,
993 })
994 .unwrap_or(HttpMethod::Post);
995
996 let query_parameters = e
997 .query_parameters
998 .into_iter()
999 .map(|qp| QueryParameter {
1000 name: qp.name,
1001 required: qp.required,
1002 })
1003 .collect();
1004
1005 StreamingEndpoint {
1006 operation_id: e.operation_id,
1007 path: e.path,
1008 http_method,
1009 stream_parameter: e.stream_parameter,
1010 query_parameters,
1011 event_union_type: e.event_union_type,
1012 content_type: e.content_type,
1013 event_flow,
1014 ..Default::default()
1015 }
1016 })
1017 .collect();
1018
1019 StreamingConfig {
1020 endpoints,
1021 ..Default::default()
1022 }
1023 });
1024
1025 GeneratorConfig {
1026 spec_path: self.generator.spec_path,
1027 output_dir: self.generator.output_dir,
1028 module_name: self.generator.module_name,
1029 enable_sse_client: self.features.enable_sse_client,
1030 enable_async_client: self.features.enable_async_client,
1031 enable_specta: self.features.enable_specta,
1032 type_mappings: if self.type_mappings.is_empty() {
1033 super::generator::default_type_mappings()
1034 } else {
1035 self.type_mappings
1036 },
1037 streaming_config,
1038 nullable_field_overrides: self.nullable_overrides,
1039 extensible_enum_overrides: self.extensible_enums,
1040 schema_extensions: self.generator.schema_extensions,
1041 overlays: self.generator.overlays,
1042 effective_spec: self.generator.effective_spec,
1043 bindings_metadata: self.generator.bindings_metadata,
1044 http_client_config,
1045 retry_config,
1046 tracing_enabled,
1047 auth_config,
1048 enable_registry: self.features.enable_registry,
1049 registry_only: self.features.registry_only,
1050 types,
1051 builders: self.generator.builders,
1052 derives: self.generator.derives,
1053 server: self.server,
1054 client: self.client,
1055 }
1056 }
1057}
1058
1059const EXAMPLE_CONFIG: &str = r#"[generator]
1060spec_path = "openapi.json"
1061output_dir = "src/generated"
1062module_name = "types"
1063
1064[generator.builders]
1065enabled = true
1066threshold = 3
1067
1068[features]
1069enable_async_client = true
1070
1071[http_client]
1072base_url = "https://api.example.com"
1073timeout_seconds = 30
1074max_response_body_bytes = 8388608
1075
1076[http_client.retry]
1077max_retries = 3
1078
1079[http_client.auth]
1080type = "Bearer"
1081header_name = "Authorization""#;