1mod axum;
8mod constraints;
9mod models;
10mod operation;
11mod package;
12mod reqwest;
13mod servers;
14mod union;
15pub(crate) mod usage;
16
17use std::collections::HashMap;
18
19use proc_macro2::TokenStream;
20use quote::quote;
21
22use crate::emit::models::ModelDerives;
23pub use crate::emit::package::emit_package;
24use crate::error::Error;
25use crate::error::Result;
26use crate::ir::Module;
27use crate::ir::Multipart;
28use crate::ir::NegotiatedBody;
29use crate::ir::RustType;
30use crate::ir::ServerUrls;
31use crate::ir::Service;
32use crate::naming::Case;
33use crate::naming::to_ident;
34
35pub trait ServerEmitter {
39 fn emit(&self, service: &Service) -> Result<Vec<TokenStream>>;
41}
42
43pub trait ClientEmitter {
48 fn emit(&self, service: &Service) -> Result<Vec<TokenStream>>;
51}
52
53macro_rules! generated_marker {
57 () => {
58 "// Code generated by oapi-codegen-rust. DO NOT EDIT."
59 };
60}
61
62pub const GENERATED_MARKER: &str = generated_marker!();
70
71pub const HEADER: &str = concat!(
92 generated_marker!(),
93 "
94#![allow(
95 dead_code,
96 unused_imports,
97 clippy::all,
98 clippy::pedantic,
99 clippy::nursery,
100 clippy::restriction,
101 reason = \"generated code, not first-party source\"
102)]
103
104"
105);
106
107pub fn emit_module(module: &Module, server_urls: Option<&ServerUrls>) -> Result<String> {
113 let mut items = module_items(module, &usage::models_only_derives(module), false)?;
117 items.extend(server_url_items(server_urls)?);
118 return render(&items);
119}
120
121#[derive(Debug, Clone, Copy, Default)]
126pub struct Targets {
127 pub server: bool,
129 pub client: bool,
131}
132
133#[derive(Debug, Clone, Copy)]
136pub struct PreludeTypeName {
137 pub name: &'static str,
139 pub used_for: &'static str,
142}
143
144pub fn prelude_type_names(targets: Targets) -> Vec<PreludeTypeName> {
162 let mut names = vec![
164 PreludeTypeName {
165 name: "Option",
166 used_for: "every optional field",
167 },
168 PreludeTypeName {
169 name: "String",
170 used_for: "every string field",
171 },
172 PreludeTypeName {
173 name: "Vec",
174 used_for: "every array field",
175 },
176 PreludeTypeName {
177 name: "Box",
178 used_for: "the indirection a recursive schema takes",
179 },
180 ];
181 if targets.server || targets.client {
182 names.push(PreludeTypeName {
183 name: "Result",
184 used_for: "every generated method signature",
185 });
186 }
187 return names;
188}
189
190#[derive(Debug, Clone, Copy)]
193pub struct ReservedTypeName {
194 pub name: &'static str,
196 pub description: &'static str,
199}
200
201pub fn reserved_type_names(targets: Targets) -> Vec<ReservedTypeName> {
205 let mut names = vec![ReservedTypeName {
206 name: "Nullable",
207 description: "nullable JSON value",
208 }];
209 if targets.server {
210 names.push(ReservedTypeName {
211 name: axum::API_TRAIT_NAME,
212 description: "server interface trait",
213 });
214 }
215 if targets.client {
216 names.push(ReservedTypeName {
217 name: reqwest::CLIENT_STRUCT_NAME,
218 description: "client struct",
219 });
220 names.push(ReservedTypeName {
221 name: reqwest::CLIENT_ERROR_NAME,
222 description: "client error enum",
223 });
224 }
225 return names;
226}
227
228pub fn emit_flat(
238 module: &Module,
239 service: &Service,
240 server_urls: Option<&ServerUrls>,
241 targets: Targets,
242) -> Result<String> {
243 let derives = usage::model_derives(module, service, targets);
244 let foreign = usage::foreign_resolver(module);
245 let mut items = module_items(module, &derives, uses_nullable(module, Some(service)))?;
246 items.extend(server_url_items(server_urls)?);
247 for operation in &service.operations {
248 items.extend(operation::emit_operation_types(operation, targets, &foreign)?);
249 }
250 if targets.server {
251 items.extend(axum::AxumServer.emit(service)?);
252 }
253 if targets.client {
254 items.extend(reqwest::ReqwestClient.emit(service)?);
255 }
256 return render(&items);
257}
258
259fn server_url_items(server_urls: Option<&ServerUrls>) -> Result<Vec<TokenStream>> {
262 return match server_urls {
263 Some(server_urls) => servers::emit_server_urls(server_urls),
264 None => Ok(Vec::new()),
265 };
266}
267
268fn module_items(
271 module: &Module,
272 derives: &HashMap<String, ModelDerives>,
273 service_nullable: bool,
274) -> Result<Vec<TokenStream>> {
275 let mut items = Vec::with_capacity(module.items.len());
276 if service_nullable || module.items.iter().any(item_uses_nullable) {
277 items.push(quote! {
278 #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, serde::Serialize)]
280 #[serde(untagged)]
281 pub enum Nullable<T> {
282 #[default]
284 Null,
285 Value(T),
287 }
288
289 impl<T> Nullable<T> {
290 pub fn as_ref(&self) -> Option<&T> {
292 return match self {
293 Self::Null => None,
294 Self::Value(value) => Some(value),
295 };
296 }
297 }
298
299 impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Nullable<T> {
300 fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
301 where
302 D: serde::Deserializer<'de>,
303 {
304 struct NullableVisitor<T>(::core::marker::PhantomData<T>);
305
306 impl<'de, T: serde::Deserialize<'de>> serde::de::Visitor<'de> for NullableVisitor<T> {
307 type Value = Nullable<T>;
308
309 fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
310 return formatter.write_str("a present value or null");
311 }
312
313 fn visit_newtype_struct<D>(self, deserializer: D) -> ::core::result::Result<Self::Value, D::Error>
314 where
315 D: serde::Deserializer<'de>,
316 {
317 return <Option<T> as serde::Deserialize>::deserialize(deserializer).map(|value| {
318 return match value {
319 Some(value) => Nullable::Value(value),
320 None => Nullable::Null,
321 };
322 });
323 }
324
325 fn visit_map<M>(self, map: M) -> ::core::result::Result<Self::Value, M::Error>
326 where
327 M: serde::de::MapAccess<'de>,
328 {
329 return T::deserialize(serde::de::value::MapAccessDeserializer::new(map))
330 .map(Nullable::Value);
331 }
332 }
333
334 return deserializer.deserialize_newtype_struct(
336 "Nullable",
337 NullableVisitor(::core::marker::PhantomData),
338 );
339 }
340 }
341 });
342 }
343 for item in &module.items {
344 let set = derives.get(item.name()).copied().unwrap_or_else(ModelDerives::both);
345 items.push(models::emit_item(item, set)?);
346 }
347 return Ok(items);
348}
349
350pub(crate) fn uses_nullable(module: &Module, service: Option<&Service>) -> bool {
351 use crate::ir::RequestPayload;
352 use crate::ir::ResponseBody;
353
354 return module.items.iter().any(item_uses_nullable)
355 || service.is_some_and(|service| {
356 return service.operations.iter().any(|operation| {
357 let request = match &operation.request {
358 Some(RequestPayload::Single(body)) => type_uses_nullable(&body.ty),
359 Some(RequestPayload::Negotiated(body)) => body
360 .variants
361 .iter()
362 .any(|variant| return type_uses_nullable(&variant.body.ty)),
363 _ => false,
364 };
365 return request
366 || operation.responses.iter().any(|response| {
367 return match &response.body {
368 Some(ResponseBody::Single(body)) => type_uses_nullable(&body.ty),
369 Some(ResponseBody::Negotiated(body)) => body
370 .variants
371 .iter()
372 .any(|variant| return type_uses_nullable(&variant.body.ty)),
373 None => false,
374 };
375 });
376 });
377 });
378}
379
380fn item_uses_nullable(item: &crate::ir::Item) -> bool {
381 return match item {
382 crate::ir::Item::Struct(value) => {
383 value.fields.iter().any(|field| return type_uses_nullable(&field.ty))
384 || value.additional_properties.as_ref().is_some_and(type_uses_nullable)
385 }
386 crate::ir::Item::Alias(value) => type_uses_nullable(&value.ty),
387 crate::ir::Item::Enum(value) => match &value.kind {
388 crate::ir::EnumKind::Union(variants) => {
389 variants.iter().any(|variant| return type_uses_nullable(&variant.ty))
390 }
391 _ => false,
392 },
393 };
394}
395
396fn type_uses_nullable(ty: &RustType) -> bool {
397 return match ty {
398 RustType::Nullable(_) => true,
399 RustType::Vec(inner) | RustType::Map(inner) | RustType::Option(inner) | RustType::Boxed(inner) => {
400 type_uses_nullable(inner)
401 }
402 _ => false,
403 };
404}
405
406fn render(items: &[TokenStream]) -> Result<String> {
409 let mut out = String::from(HEADER);
410 out.push_str(&render_body(items)?);
411 return Ok(out);
412}
413
414fn render_body(items: &[TokenStream]) -> Result<String> {
420 let mut out = String::new();
421 for (index, tokens) in items.iter().enumerate() {
422 let file = syn::parse2::<syn::File>(tokens.clone()).map_err(|source| {
423 return Error::InvalidGeneratedCode { source };
424 })?;
425 if index > 0 {
426 out.push('\n');
427 }
428 out.push_str(&prettyplease::unparse(&file));
429 }
430 return Ok(out);
431}
432
433pub(crate) fn doc_attr(doc: &Option<String>) -> TokenStream {
438 let tokens = match doc {
439 Some(text) => doc_lines(std::slice::from_ref(text)),
440 None => quote! {},
441 };
442 return tokens;
443}
444
445pub(crate) fn doc_lines(lines: &[String]) -> TokenStream {
452 let attrs = lines.iter().flat_map(|entry| return entry.split('\n')).map(|line| {
453 let trimmed = line.trim_end();
456 let spaced = if trimmed.is_empty() {
457 String::new()
458 } else {
459 format!(" {trimmed}")
460 };
461 return quote! { #[doc = #spaced] };
462 });
463 return quote! { #(#attrs)* };
464}
465
466pub(crate) fn emit_type(ty: &RustType) -> Result<TokenStream> {
468 let tokens = match ty {
469 RustType::Bool => quote! { bool },
470 RustType::I32 => quote! { i32 },
471 RustType::I64 => quote! { i64 },
472 RustType::U32 => quote! { u32 },
473 RustType::U64 => quote! { u64 },
474 RustType::F64 => quote! { f64 },
475 RustType::String => quote! { String },
476 RustType::Value => quote! { serde_json::Value },
477 RustType::Date => quote! { chrono::NaiveDate },
478 RustType::DateTime => quote! { chrono::DateTime<chrono::Utc> },
479 RustType::Uuid => quote! { uuid::Uuid },
480 RustType::Bytes => quote! { Vec<u8> },
481 RustType::Vec(inner) => {
482 let inner = emit_type(inner)?;
483 quote! { Vec<#inner> }
484 }
485 RustType::Map(inner) => {
486 let inner = emit_type(inner)?;
487 quote! { std::collections::HashMap<String, #inner> }
488 }
489 RustType::Option(inner) => {
490 let inner = emit_type(inner)?;
491 quote! { Option<#inner> }
492 }
493 RustType::Nullable(inner) => {
494 let inner = emit_type(inner)?;
495 quote! { Nullable<#inner> }
496 }
497 RustType::Boxed(inner) => {
498 let inner = emit_type(inner)?;
499 quote! { Box<#inner> }
500 }
501 RustType::Named(name) => {
502 let ident = to_ident(name, Case::Pascal).to_token();
503 quote! { #ident }
504 }
505 RustType::External { module, name } => {
506 let path: syn::Path = syn::parse_str(module).map_err(|err| {
507 return Error::UnsupportedSchema {
508 path: "import-mapping".to_owned(),
509 reason: format!("module path `{module}` is not a valid Rust path expression: {err}"),
510 };
511 })?;
512 let ident = to_ident(name, Case::Pascal).to_token();
513 quote! { #path::#ident }
514 }
515 RustType::Verbatim { text, .. } => {
516 let parsed: TokenStream = text.parse().map_err(|err: proc_macro2::LexError| {
517 return Error::UnsupportedSchema {
518 path: "x-rust-type".to_owned(),
519 reason: format!("value `{text}` is not a valid Rust type expression: {err}"),
520 };
521 })?;
522 parsed
523 }
524 };
525 return Ok(tokens);
526}
527
528pub(crate) fn emit_multipart_struct(multipart: &Multipart, foreign: &usage::ForeignResolver) -> Result<TokenStream> {
533 let name = multipart.name.to_token();
534 let field_types = multipart.fields.iter().map(|field| return &field.ty);
535 let derive_attr = models::plain_derive_attr(models::DEBUG_AND_CLONE, foreign.of_types(field_types));
536 let mut field_defs = Vec::with_capacity(multipart.fields.len());
537 for field in &multipart.fields {
538 let ident = field.rust_name.to_token();
539 let ty = emit_type(&field.ty)?;
540 let field_ty = if field.optional {
541 quote! { Option<#ty> }
542 } else {
543 quote! { #ty }
544 };
545 field_defs.push(quote! { pub #ident: #field_ty, });
546 }
547 return Ok(quote! {
548 #derive_attr
549 pub struct #name {
550 #(#field_defs)*
551 }
552 });
553}
554
555pub(crate) fn emit_negotiated_body_enum(
561 body: &NegotiatedBody,
562 foreign: &usage::ForeignResolver,
563) -> Result<TokenStream> {
564 let name = body.name.to_token();
565 let variant_types = body.variants.iter().map(|variant| return &variant.body.ty);
566 let derive_attr = models::plain_derive_attr(models::DEBUG_CLONE_AND_EQ, foreign.of_types(variant_types));
567 let mut variants = Vec::with_capacity(body.variants.len());
568 for variant in &body.variants {
569 let ident = variant.variant.to_token();
570 let ty = emit_type(&variant.body.ty)?;
571 variants.push(quote! { #ident(#ty) });
572 }
573 return Ok(quote! {
574 #derive_attr
575 pub enum #name {
576 #(#variants),*
577 }
578 });
579}