Skip to main content

reinhardt_rest/metadata/
base.rs

1//! Base metadata trait and implementations
2
3use super::fields::FieldInfo;
4use super::options::MetadataOptions;
5use super::response::MetadataResponse;
6use async_trait::async_trait;
7use reinhardt_core::exception::Result;
8use reinhardt_http::Request;
9use std::collections::HashMap;
10
11/// Base trait for metadata providers
12#[async_trait]
13pub trait BaseMetadata: Send + Sync {
14	/// Determine metadata for a view based on the request
15	async fn determine_metadata(
16		&self,
17		request: &Request,
18		options: &MetadataOptions,
19	) -> Result<MetadataResponse>;
20}
21
22/// Simple metadata implementation
23///
24/// This is the default metadata implementation that returns
25/// basic information about the view and its fields.
26#[derive(Debug, Clone)]
27pub struct SimpleMetadata {
28	/// Whether to include action metadata (POST/PUT fields) in the response.
29	pub include_actions: bool,
30}
31
32impl SimpleMetadata {
33	/// Creates a new `SimpleMetadata` instance with actions enabled by default
34	///
35	/// # Examples
36	///
37	/// ```
38	/// use reinhardt_rest::metadata::SimpleMetadata;
39	///
40	/// let metadata = SimpleMetadata::new();
41	/// assert!(metadata.include_actions);
42	/// ```
43	pub fn new() -> Self {
44		Self {
45			include_actions: true,
46		}
47	}
48	/// Configures whether to include actions in metadata responses
49	///
50	/// # Examples
51	///
52	/// ```
53	/// use reinhardt_rest::metadata::SimpleMetadata;
54	///
55	/// let metadata = SimpleMetadata::new().with_actions(false);
56	/// assert!(!metadata.include_actions);
57	///
58	/// let metadata_with_actions = SimpleMetadata::new().with_actions(true);
59	/// assert!(metadata_with_actions.include_actions);
60	/// ```
61	pub fn with_actions(mut self, include: bool) -> Self {
62		self.include_actions = include;
63		self
64	}
65	/// Convert serializer field information to metadata field information
66	///
67	/// This method transforms serializer field metadata into the format expected
68	/// by the metadata system, using type inference to determine field types.
69	///
70	/// # Arguments
71	///
72	/// * `serializer_fields` - Map of field names to serializer field information
73	///
74	/// # Examples
75	///
76	/// ```
77	/// use reinhardt_rest::metadata::{SimpleMetadata, SerializerFieldInfo};
78	/// use std::collections::HashMap;
79	///
80	/// let metadata = SimpleMetadata::new();
81	/// let mut serializer_fields = HashMap::new();
82	/// serializer_fields.insert(
83	///     "username".to_string(),
84	///     SerializerFieldInfo {
85	///         name: "username".to_string(),
86	///         type_name: "String".to_string(),
87	///         is_optional: false,
88	///         is_read_only: false,
89	///         is_write_only: false,
90	///     }
91	/// );
92	///
93	/// let fields = metadata.convert_serializer_fields(&serializer_fields);
94	/// assert_eq!(fields.len(), 1);
95	/// ```
96	pub fn convert_serializer_fields(
97		&self,
98		serializer_fields: &HashMap<String, super::options::SerializerFieldInfo>,
99	) -> HashMap<String, FieldInfo> {
100		use super::inferencer::SchemaInferencer;
101
102		let inferencer = SchemaInferencer::new();
103		let mut fields = HashMap::new();
104
105		for (field_name, serializer_field) in serializer_fields {
106			// Use type inference to determine field type
107			let mut field_info = inferencer.infer_from_type_name(&serializer_field.type_name);
108
109			// Override required status based on is_optional
110			field_info.required = !serializer_field.is_optional;
111
112			// Set read_only and write_only flags
113			field_info.read_only = Some(serializer_field.is_read_only);
114			// Note: metadata FieldInfo doesn't have write_only field yet
115			// This could be added in the future if needed
116
117			fields.insert(field_name.clone(), field_info);
118		}
119
120		fields
121	}
122
123	/// Determine which actions should be available based on allowed methods
124	///
125	/// # Examples
126	///
127	/// ```
128	/// use reinhardt_rest::metadata::{SimpleMetadata, FieldInfoBuilder, FieldType};
129	/// use std::collections::HashMap;
130	///
131	/// let metadata = SimpleMetadata::new();
132	/// let mut fields = HashMap::new();
133	/// fields.insert(
134	///     "username".to_string(),
135	///     FieldInfoBuilder::new(FieldType::String).required(true).build()
136	/// );
137	///
138	/// let allowed_methods = vec!["GET".to_string(), "POST".to_string(), "PUT".to_string()];
139	/// let actions = metadata.determine_actions(&allowed_methods, &fields);
140	///
141	/// // GET is not included in actions, only POST and PUT
142	/// assert!(!actions.contains_key("GET"));
143	/// assert!(actions.contains_key("POST"));
144	/// assert!(actions.contains_key("PUT"));
145	/// assert_eq!(actions["POST"].len(), 1);
146	/// ```
147	pub fn determine_actions(
148		&self,
149		allowed_methods: &[String],
150		fields: &HashMap<String, FieldInfo>,
151	) -> HashMap<String, HashMap<String, FieldInfo>> {
152		let mut actions = HashMap::new();
153
154		for method in allowed_methods {
155			let method_upper = method.to_uppercase();
156			if method_upper == "POST" || method_upper == "PUT" || method_upper == "PATCH" {
157				actions.insert(method_upper, fields.clone());
158			}
159		}
160
161		actions
162	}
163}
164
165impl Default for SimpleMetadata {
166	fn default() -> Self {
167		Self::new()
168	}
169}
170
171#[async_trait]
172impl BaseMetadata for SimpleMetadata {
173	async fn determine_metadata(
174		&self,
175		_request: &Request,
176		options: &MetadataOptions,
177	) -> Result<MetadataResponse> {
178		let mut response = MetadataResponse {
179			name: options.name.clone(),
180			description: options.description.clone(),
181			renders: Some(options.renders.clone()),
182			parses: Some(options.parses.clone()),
183			actions: None,
184		};
185
186		if self.include_actions {
187			// Inspect serializer fields to get field metadata
188			let fields = if let Some(serializer_fields) = &options.serializer_fields {
189				self.convert_serializer_fields(serializer_fields)
190			} else {
191				HashMap::new()
192			};
193
194			let actions = self.determine_actions(&options.allowed_methods, &fields);
195			if !actions.is_empty() {
196				response.actions = Some(actions);
197			}
198		}
199
200		Ok(response)
201	}
202}
203
204#[cfg(test)]
205mod tests {
206	use super::*;
207	use crate::metadata::fields::FieldInfoBuilder;
208	use crate::metadata::types::{ChoiceInfo, FieldType};
209	use bytes::Bytes;
210	use hyper::{HeaderMap, Method, Version};
211
212	fn create_test_request() -> Request {
213		Request::builder()
214			.method(Method::OPTIONS)
215			.uri("/users/")
216			.version(Version::HTTP_11)
217			.headers(HeaderMap::new())
218			.body(Bytes::new())
219			.build()
220			.unwrap()
221	}
222
223	// DRF test: test_determine_metadata_abstract_method_raises_proper_error
224	// BaseMetadata is a trait in Rust, so we test implementation requirements instead
225	#[tokio::test]
226	async fn test_base_metadata_trait_requires_implementation() {
227		// This test verifies that BaseMetadata trait requires determine_metadata implementation
228		let metadata = SimpleMetadata::new();
229		let request = create_test_request();
230		let options = MetadataOptions::default();
231
232		// Should successfully call determine_metadata on a concrete implementation
233		let result = metadata.determine_metadata(&request, &options).await;
234		assert!(result.is_ok());
235	}
236
237	// DRF test: test_metadata
238	// OPTIONS requests should return valid 200 response with metadata
239	#[tokio::test]
240	async fn test_metadata_basic_response() {
241		let metadata = SimpleMetadata::new();
242		let request = create_test_request();
243		let options = MetadataOptions {
244			name: "Example".to_string(),
245			description: "Example view.".to_string(),
246			allowed_methods: vec!["GET".to_string()],
247			renders: vec!["application/json".to_string(), "text/html".to_string()],
248			parses: vec![
249				"application/json".to_string(),
250				"application/x-www-form-urlencoded".to_string(),
251				"multipart/form-data".to_string(),
252			],
253			serializer_fields: None,
254		};
255
256		let response = metadata
257			.determine_metadata(&request, &options)
258			.await
259			.unwrap();
260
261		assert_eq!(response.name, "Example");
262		assert_eq!(response.description, "Example view.");
263		assert_eq!(
264			response.renders,
265			Some(vec![
266				"application/json".to_string(),
267				"text/html".to_string()
268			])
269		);
270		assert_eq!(
271			response.parses,
272			Some(vec![
273				"application/json".to_string(),
274				"application/x-www-form-urlencoded".to_string(),
275				"multipart/form-data".to_string(),
276			])
277		);
278	}
279
280	// DRF test: test_actions
281	// OPTIONS should return 'actions' key with field metadata for POST/PUT
282	#[tokio::test]
283	async fn test_actions_with_fields() {
284		let metadata = SimpleMetadata::new();
285
286		let mut fields = HashMap::new();
287
288		// choice_field
289		fields.insert(
290			"choice_field".to_string(),
291			FieldInfoBuilder::new(FieldType::Choice)
292				.required(true)
293				.read_only(false)
294				.label("Choice field")
295				.choices(vec![
296					ChoiceInfo {
297						value: "red".to_string(),
298						display_name: "red".to_string(),
299					},
300					ChoiceInfo {
301						value: "green".to_string(),
302						display_name: "green".to_string(),
303					},
304					ChoiceInfo {
305						value: "blue".to_string(),
306						display_name: "blue".to_string(),
307					},
308				])
309				.build(),
310		);
311
312		// integer_field
313		fields.insert(
314			"integer_field".to_string(),
315			FieldInfoBuilder::new(FieldType::Integer)
316				.required(true)
317				.read_only(false)
318				.label("Integer field")
319				.min_value(1.0)
320				.max_value(1000.0)
321				.build(),
322		);
323
324		// char_field
325		fields.insert(
326			"char_field".to_string(),
327			FieldInfoBuilder::new(FieldType::String)
328				.required(false)
329				.read_only(false)
330				.label("Char field")
331				.min_length(3)
332				.max_length(40)
333				.build(),
334		);
335
336		// nested_field
337		let mut nested_children = HashMap::new();
338		nested_children.insert(
339			"a".to_string(),
340			FieldInfoBuilder::new(FieldType::Integer)
341				.required(true)
342				.read_only(false)
343				.label("A")
344				.build(),
345		);
346		nested_children.insert(
347			"b".to_string(),
348			FieldInfoBuilder::new(FieldType::Integer)
349				.required(true)
350				.read_only(false)
351				.label("B")
352				.build(),
353		);
354
355		fields.insert(
356			"nested_field".to_string(),
357			FieldInfoBuilder::new(FieldType::NestedObject)
358				.required(true)
359				.read_only(false)
360				.label("Nested field")
361				.children(nested_children)
362				.build(),
363		);
364
365		let options = MetadataOptions {
366			name: "Example".to_string(),
367			description: "Example view.".to_string(),
368			allowed_methods: vec!["POST".to_string()],
369			renders: vec!["application/json".to_string()],
370			parses: vec!["application/json".to_string()],
371			serializer_fields: None,
372		};
373
374		let actions = metadata.determine_actions(&options.allowed_methods, &fields);
375
376		assert!(actions.contains_key("POST"));
377		let post_fields = &actions["POST"];
378		assert!(post_fields.contains_key("choice_field"));
379		assert!(post_fields.contains_key("integer_field"));
380		assert!(post_fields.contains_key("char_field"));
381		assert!(post_fields.contains_key("nested_field"));
382
383		// Verify choice field
384		let choice_field = &post_fields["choice_field"];
385		assert_eq!(choice_field.field_type, FieldType::Choice);
386		assert!(choice_field.required);
387		assert_eq!(choice_field.read_only, Some(false));
388		assert_eq!(choice_field.choices.as_ref().unwrap().len(), 3);
389
390		// Verify integer field
391		let integer_field = &post_fields["integer_field"];
392		assert_eq!(integer_field.field_type, FieldType::Integer);
393		assert_eq!(integer_field.min_value, Some(1.0));
394		assert_eq!(integer_field.max_value, Some(1000.0));
395
396		// Verify nested field
397		let nested_field = &post_fields["nested_field"];
398		assert_eq!(nested_field.field_type, FieldType::NestedObject);
399		assert!(nested_field.children.is_some());
400		let children = nested_field.children.as_ref().unwrap();
401		assert!(children.contains_key("a"));
402		assert!(children.contains_key("b"));
403	}
404
405	#[tokio::test]
406	async fn test_simple_metadata() {
407		let metadata = SimpleMetadata::new();
408		let request = create_test_request();
409		let options = MetadataOptions {
410			name: "User List".to_string(),
411			description: "List all users".to_string(),
412			allowed_methods: vec!["GET".to_string(), "POST".to_string()],
413			renders: vec!["application/json".to_string()],
414			parses: vec!["application/json".to_string()],
415			serializer_fields: None,
416		};
417
418		let response = metadata
419			.determine_metadata(&request, &options)
420			.await
421			.unwrap();
422
423		assert_eq!(response.name, "User List");
424		assert_eq!(response.description, "List all users");
425		assert_eq!(response.renders, Some(vec!["application/json".to_string()]));
426		assert_eq!(response.parses, Some(vec!["application/json".to_string()]));
427	}
428
429	// DRF test: test_metadata_with_serializer_inspection
430	// Test that serializer fields are properly inspected and converted to metadata
431	#[tokio::test]
432	async fn test_metadata_with_serializer_inspection() {
433		use crate::metadata::options::SerializerFieldInfo;
434
435		let metadata = SimpleMetadata::new();
436		let request = create_test_request();
437
438		// Simulate serializer field introspection
439		let mut serializer_fields = HashMap::new();
440		serializer_fields.insert(
441			"username".to_string(),
442			SerializerFieldInfo {
443				name: "username".to_string(),
444				type_name: "String".to_string(),
445				is_optional: false,
446				is_read_only: false,
447				is_write_only: false,
448			},
449		);
450		serializer_fields.insert(
451			"email".to_string(),
452			SerializerFieldInfo {
453				name: "email".to_string(),
454				type_name: "String".to_string(),
455				is_optional: false,
456				is_read_only: false,
457				is_write_only: false,
458			},
459		);
460		serializer_fields.insert(
461			"age".to_string(),
462			SerializerFieldInfo {
463				name: "age".to_string(),
464				type_name: "i32".to_string(),
465				is_optional: true,
466				is_read_only: false,
467				is_write_only: false,
468			},
469		);
470
471		let options = MetadataOptions {
472			name: "User Create".to_string(),
473			description: "Create a new user".to_string(),
474			allowed_methods: vec!["POST".to_string()],
475			renders: vec!["application/json".to_string()],
476			parses: vec!["application/json".to_string()],
477			serializer_fields: Some(serializer_fields),
478		};
479
480		let response = metadata
481			.determine_metadata(&request, &options)
482			.await
483			.unwrap();
484
485		// Verify basic metadata
486		assert_eq!(response.name, "User Create");
487		assert_eq!(response.description, "Create a new user");
488
489		// Verify actions were generated
490		assert!(response.actions.is_some());
491		let actions = response.actions.unwrap();
492		assert!(actions.contains_key("POST"));
493
494		// Verify POST action fields
495		let post_fields = &actions["POST"];
496		assert_eq!(post_fields.len(), 3);
497
498		// Verify username field
499		assert!(post_fields.contains_key("username"));
500		let username_field = &post_fields["username"];
501		assert_eq!(username_field.field_type, FieldType::String);
502		assert!(username_field.required);
503		assert_eq!(username_field.read_only, Some(false));
504
505		// Verify email field
506		assert!(post_fields.contains_key("email"));
507		let email_field = &post_fields["email"];
508		assert_eq!(email_field.field_type, FieldType::String);
509		assert!(email_field.required);
510
511		// Verify age field (optional)
512		assert!(post_fields.contains_key("age"));
513		let age_field = &post_fields["age"];
514		assert_eq!(age_field.field_type, FieldType::Integer);
515		assert!(!age_field.required); // is_optional: true
516		assert_eq!(age_field.read_only, Some(false));
517	}
518}