Skip to content

Commit ce93799

Browse files
committed
Add default
1 parent 4022260 commit ce93799

3 files changed

Lines changed: 134 additions & 110 deletions

File tree

crates/vespera_core/src/openapi.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub struct License {
4848
}
4949

5050
/// API information
51-
#[derive(Debug, Clone, Serialize, Deserialize)]
51+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5252
#[serde(rename_all = "camelCase")]
5353
pub struct Info {
5454
/// API title
@@ -245,16 +245,7 @@ mod tests {
245245
responses: BTreeMap::new(),
246246
security: None,
247247
}),
248-
post: None,
249-
put: None,
250-
delete: None,
251-
patch: None,
252-
options: None,
253-
head: None,
254-
trace: None,
255-
parameters: None,
256-
summary: None,
257-
description: None,
248+
..Default::default()
258249
}
259250
}
260251

crates/vespera_core/src/route.rs

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ pub struct Operation {
184184
}
185185

186186
/// Path Item definition (all HTTP methods for a specific path)
187-
#[derive(Debug, Clone, Serialize, Deserialize)]
187+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188188
#[serde(rename_all = "camelCase")]
189189
pub struct PathItem {
190190
/// GET method
@@ -336,19 +336,7 @@ mod tests {
336336

337337
#[test]
338338
fn test_path_item_set_operation() {
339-
let mut path_item = PathItem {
340-
get: None,
341-
post: None,
342-
put: None,
343-
patch: None,
344-
delete: None,
345-
head: None,
346-
options: None,
347-
trace: None,
348-
parameters: None,
349-
summary: None,
350-
description: None,
351-
};
339+
let mut path_item = PathItem::default();
352340

353341
let operation = Operation {
354342
operation_id: Some("test_operation".to_string()),
@@ -418,19 +406,7 @@ mod tests {
418406

419407
#[test]
420408
fn test_path_item_get_operation() {
421-
let mut path_item = PathItem {
422-
get: None,
423-
post: None,
424-
put: None,
425-
patch: None,
426-
delete: None,
427-
head: None,
428-
options: None,
429-
trace: None,
430-
parameters: None,
431-
summary: None,
432-
description: None,
433-
};
409+
let mut path_item = PathItem::default();
434410

435411
let operation = Operation {
436412
operation_id: Some("test_operation".to_string()),
@@ -489,19 +465,7 @@ mod tests {
489465

490466
#[test]
491467
fn test_path_item_set_operation_overwrites() {
492-
let mut path_item = PathItem {
493-
get: None,
494-
post: None,
495-
put: None,
496-
patch: None,
497-
delete: None,
498-
head: None,
499-
options: None,
500-
trace: None,
501-
parameters: None,
502-
summary: None,
503-
description: None,
504-
};
468+
let mut path_item = PathItem::default();
505469

506470
let operation1 = Operation {
507471
operation_id: Some("first".to_string()),

crates/vespera_macro/src/openapi_generator.rs

Lines changed: 128 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,7 @@ pub fn generate_openapi_doc_with_metadata(
4848
info: Info {
4949
title: title.unwrap_or_else(|| "API".to_string()),
5050
version: version.unwrap_or_else(|| "1.0.0".to_string()),
51-
description: None,
52-
terms_of_service: None,
53-
contact: None,
54-
license: None,
55-
summary: None,
51+
..Default::default()
5652
},
5753
servers: servers.or_else(|| {
5854
Some(vec![Server {
@@ -250,19 +246,7 @@ fn build_path_items(
250246

251247
let path_item = paths
252248
.entry(route_meta.path.clone())
253-
.or_insert_with(|| PathItem {
254-
get: None,
255-
post: None,
256-
put: None,
257-
patch: None,
258-
delete: None,
259-
head: None,
260-
options: None,
261-
trace: None,
262-
parameters: None,
263-
summary: None,
264-
description: None,
265-
});
249+
.or_insert_with(PathItem::default);
266250

267251
path_item.set_operation(method, operation);
268252
break;
@@ -273,6 +257,24 @@ fn build_path_items(
273257
(paths, all_tags)
274258
}
275259

260+
/// Set the default value on an inline property schema, if not already set.
261+
///
262+
/// Looks up `field_name` in the properties map. If found as an inline schema
263+
/// and the schema has no existing default, sets `value` as the default.
264+
fn set_property_default(
265+
properties: &mut BTreeMap<String, vespera_core::schema::SchemaRef>,
266+
field_name: &str,
267+
value: serde_json::Value,
268+
) {
269+
use vespera_core::schema::SchemaRef;
270+
271+
if let Some(SchemaRef::Inline(prop_schema)) = properties.get_mut(field_name)
272+
&& prop_schema.default.is_none()
273+
{
274+
prop_schema.default = Some(value);
275+
}
276+
}
277+
276278
/// Process default functions for struct fields
277279
/// This function extracts default values from:
278280
/// 1. `#[schema(default = "value")]` attributes (generated by `schema_type!` from `sea_orm(default_value)`)
@@ -284,7 +286,6 @@ fn process_default_functions(
284286
schema: &mut vespera_core::schema::Schema,
285287
) {
286288
use syn::Fields;
287-
use vespera_core::schema::SchemaRef;
288289

289290
// Extract rename_all from struct level
290291
let struct_rename_all = extract_rename_all(&struct_item.attrs);
@@ -308,12 +309,7 @@ fn process_default_functions(
308309
// Priority 1: #[schema(default = "value")] from schema_type! macro
309310
if let Some(default_str) = extract_schema_default_attr(&field.attrs) {
310311
let value = parse_default_string_to_json_value(&default_str);
311-
if let Some(prop_schema_ref) = properties.get_mut(&field_name)
312-
&& let SchemaRef::Inline(prop_schema) = prop_schema_ref
313-
&& prop_schema.default.is_none()
314-
{
315-
prop_schema.default = Some(value);
316-
}
312+
set_property_default(properties, &field_name, value);
317313
continue;
318314
}
319315

@@ -322,30 +318,19 @@ fn process_default_functions(
322318
Some(Some(func_name)) => func_name, // default = "function_name"
323319
Some(None) => {
324320
// Simple default (no function) - we can set type-specific defaults
325-
if let Some(prop_schema_ref) = properties.get_mut(&field_name)
326-
&& let SchemaRef::Inline(prop_schema) = prop_schema_ref
327-
&& prop_schema.default.is_none()
328-
&& let Some(default_value) = utils_get_type_default(&field.ty)
329-
{
330-
prop_schema.default = Some(default_value);
321+
if let Some(default_value) = utils_get_type_default(&field.ty) {
322+
set_property_default(properties, &field_name, default_value);
331323
}
332324
continue;
333325
}
334326
None => continue, // No default attribute
335327
};
336328

337-
// Find the function in the file AST
338-
let func = find_function_in_file(file_ast, &default_info);
339-
if let Some(func_item) = func {
340-
// Extract default value from function body
341-
if let Some(default_value) = extract_default_value_from_function(func_item) {
342-
// Set default value in schema
343-
if let Some(prop_schema_ref) = properties.get_mut(&field_name)
344-
&& let SchemaRef::Inline(prop_schema) = prop_schema_ref
345-
{
346-
prop_schema.default = Some(default_value);
347-
}
348-
}
329+
// Find the function in the file AST and extract default value
330+
if let Some(func_item) = find_function_in_file(file_ast, &default_info)
331+
&& let Some(default_value) = extract_default_value_from_function(func_item)
332+
{
333+
set_property_default(properties, &field_name, default_value);
349334
}
350335
}
351336
}
@@ -356,8 +341,10 @@ fn process_default_functions(
356341
/// This attribute is generated by `schema_type!` when converting `sea_orm(default_value)`.
357342
/// It carries the raw default value string for OpenAPI schema generation.
358343
fn extract_schema_default_attr(attrs: &[syn::Attribute]) -> Option<String> {
359-
for attr in attrs {
360-
if attr.path().is_ident("schema") {
344+
attrs
345+
.iter()
346+
.filter(|attr| attr.path().is_ident("schema"))
347+
.find_map(|attr| {
361348
let mut default_value = None;
362349
let _ = attr.parse_nested_meta(|meta| {
363350
if meta.path.is_ident("default") {
@@ -367,12 +354,8 @@ fn extract_schema_default_attr(attrs: &[syn::Attribute]) -> Option<String> {
367354
}
368355
Ok(())
369356
});
370-
if default_value.is_some() {
371-
return default_value;
372-
}
373-
}
374-
}
375-
None
357+
default_value
358+
})
376359
}
377360

378361
/// Parse a default value string into the appropriate `serde_json::Value`.
@@ -402,14 +385,10 @@ fn find_function_in_file<'a>(
402385
file_ast: &'a syn::File,
403386
function_name: &str,
404387
) -> Option<&'a syn::ItemFn> {
405-
for item in &file_ast.items {
406-
if let syn::Item::Fn(fn_item) = item
407-
&& fn_item.sig.ident == function_name
408-
{
409-
return Some(fn_item);
410-
}
411-
}
412-
None
388+
file_ast.items.iter().find_map(|item| match item {
389+
syn::Item::Fn(fn_item) if fn_item.sig.ident == function_name => Some(fn_item),
390+
_ => None,
391+
})
413392
}
414393

415394
/// Extract default value from function body
@@ -1504,4 +1483,94 @@ pub fn create_users() -> String {
15041483
// The unparseable definition should be skipped
15051484
assert!(doc.components.is_none() || doc.components.as_ref().unwrap().schemas.is_none());
15061485
}
1486+
1487+
// ======== Tests for set_property_default helper ========
1488+
1489+
#[test]
1490+
fn test_set_property_default_on_inline_schema() {
1491+
use vespera_core::schema::{Schema, SchemaRef};
1492+
1493+
let mut properties = BTreeMap::new();
1494+
let mut schema = Schema::object();
1495+
schema.default = None;
1496+
properties.insert("name".to_string(), SchemaRef::Inline(Box::new(schema)));
1497+
1498+
set_property_default(
1499+
&mut properties,
1500+
"name",
1501+
serde_json::Value::String("Alice".to_string()),
1502+
);
1503+
1504+
if let Some(SchemaRef::Inline(prop)) = properties.get("name") {
1505+
assert_eq!(
1506+
prop.default,
1507+
Some(serde_json::Value::String("Alice".to_string()))
1508+
);
1509+
} else {
1510+
panic!("Expected Inline schema");
1511+
}
1512+
}
1513+
1514+
#[test]
1515+
fn test_set_property_default_does_not_overwrite_existing() {
1516+
use vespera_core::schema::{Schema, SchemaRef};
1517+
1518+
let mut properties = BTreeMap::new();
1519+
let mut schema = Schema::object();
1520+
schema.default = Some(serde_json::Value::String("existing".to_string()));
1521+
properties.insert("name".to_string(), SchemaRef::Inline(Box::new(schema)));
1522+
1523+
set_property_default(
1524+
&mut properties,
1525+
"name",
1526+
serde_json::Value::String("new".to_string()),
1527+
);
1528+
1529+
if let Some(SchemaRef::Inline(prop)) = properties.get("name") {
1530+
assert_eq!(
1531+
prop.default,
1532+
Some(serde_json::Value::String("existing".to_string())),
1533+
"Should NOT overwrite existing default"
1534+
);
1535+
} else {
1536+
panic!("Expected Inline schema");
1537+
}
1538+
}
1539+
1540+
#[test]
1541+
fn test_set_property_default_skips_ref_schema() {
1542+
use vespera_core::schema::{Reference, SchemaRef};
1543+
1544+
let mut properties = BTreeMap::new();
1545+
properties.insert(
1546+
"user".to_string(),
1547+
SchemaRef::Ref(Reference::schema("User")),
1548+
);
1549+
1550+
// Should silently no-op (Ref variants have no default field)
1551+
set_property_default(
1552+
&mut properties,
1553+
"user",
1554+
serde_json::Value::String("ignored".to_string()),
1555+
);
1556+
1557+
assert!(
1558+
matches!(properties.get("user"), Some(SchemaRef::Ref(_))),
1559+
"Should remain a Ref variant"
1560+
);
1561+
}
1562+
1563+
#[test]
1564+
fn test_set_property_default_skips_missing_property() {
1565+
let mut properties = BTreeMap::new();
1566+
1567+
// Should silently no-op (property doesn't exist)
1568+
set_property_default(
1569+
&mut properties,
1570+
"nonexistent",
1571+
serde_json::Value::Number(42.into()),
1572+
);
1573+
1574+
assert!(properties.is_empty(), "Should not insert new properties");
1575+
}
15071576
}

0 commit comments

Comments
 (0)