1pub use sekkei::{Components, OpenApiSpec, Operation, PathItem};
5
6pub use takumi::FieldType as TypeInfo;
8
9#[must_use]
11pub(crate) fn ref_name_from_path(ref_path: &str) -> Option<&str> {
12 ref_path.rsplit('/').next()
13}
14
15#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub enum SchemaOrRef {
23 Ref { ref_path: String },
24 Schema(Box<sekkei::Schema>),
25}
26
27impl SchemaOrRef {
28 #[must_use]
30 pub fn ref_name(&self) -> Option<&str> {
31 match self {
32 Self::Ref { ref_path } => ref_name_from_path(ref_path),
33 Self::Schema(_) => None,
34 }
35 }
36
37 #[must_use]
42 #[deprecated(since = "0.2.0", note = "use `SchemaOrRef::from(schema)` instead")]
43 pub fn from_schema(schema: &sekkei::Schema) -> Self {
44 Self::from(schema)
45 }
46}
47
48impl From<&sekkei::Schema> for SchemaOrRef {
49 fn from(schema: &sekkei::Schema) -> Self {
50 if let Some(ref_path) = &schema.ref_path {
51 Self::Ref {
52 ref_path: ref_path.clone(),
53 }
54 } else {
55 Self::Schema(Box::new(schema.clone()))
56 }
57 }
58}
59
60pub type SchemaObject = sekkei::Schema;
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn ref_name_extracts_last_segment() {
69 let sor = SchemaOrRef::Ref {
70 ref_path: "#/components/schemas/Foo".to_string(),
71 };
72 assert_eq!(sor.ref_name(), Some("Foo"));
73 }
74
75 #[test]
76 fn ref_name_single_segment_ref() {
77 let sor = SchemaOrRef::Ref {
78 ref_path: "JustAName".to_string(),
79 };
80 assert_eq!(sor.ref_name(), Some("JustAName"));
81 }
82
83 #[test]
84 fn ref_name_empty_trailing_slash() {
85 let sor = SchemaOrRef::Ref {
86 ref_path: "#/components/schemas/".to_string(),
87 };
88 assert_eq!(sor.ref_name(), Some(""));
89 }
90
91 #[test]
92 fn ref_name_returns_none_for_schema_variant() {
93 let schema = sekkei::Schema::default();
94 let sor = SchemaOrRef::Schema(Box::new(schema));
95 assert_eq!(sor.ref_name(), None);
96 }
97
98 #[test]
99 #[allow(deprecated)]
100 fn from_schema_with_ref_path_produces_ref_variant() {
101 let schema = sekkei::Schema {
102 ref_path: Some("#/components/schemas/Bar".to_string()),
103 ..sekkei::Schema::default()
104 };
105 let sor = SchemaOrRef::from_schema(&schema);
106 match &sor {
107 SchemaOrRef::Ref { ref_path } => {
108 assert_eq!(ref_path, "#/components/schemas/Bar");
109 }
110 SchemaOrRef::Schema(_) => panic!("expected Ref variant"),
111 }
112 }
113
114 #[test]
115 #[allow(deprecated)]
116 fn from_schema_without_ref_path_produces_schema_variant() {
117 let schema = sekkei::Schema::default();
118 let sor = SchemaOrRef::from_schema(&schema);
119 assert!(matches!(sor, SchemaOrRef::Schema(_)));
120 }
121
122 #[test]
123 #[allow(deprecated)]
124 fn from_schema_preserves_schema_type() {
125 let schema = sekkei::Schema {
126 schema_type: Some("string".to_string()),
127 description: Some("a test field".to_string()),
128 ..sekkei::Schema::default()
129 };
130 let sor = SchemaOrRef::from_schema(&schema);
131 match sor {
132 SchemaOrRef::Schema(s) => {
133 assert_eq!(s.schema_type.as_deref(), Some("string"));
134 assert_eq!(s.description.as_deref(), Some("a test field"));
135 }
136 SchemaOrRef::Ref { .. } => panic!("expected Schema variant"),
137 }
138 }
139
140 #[test]
141 fn schema_or_ref_debug_impl() {
142 let sor = SchemaOrRef::Ref {
143 ref_path: "#/test".to_string(),
144 };
145 let dbg = format!("{sor:?}");
146 assert!(dbg.contains("Ref"));
147 assert!(dbg.contains("#/test"));
148 }
149
150 #[test]
151 fn schema_or_ref_clone() {
152 let sor = SchemaOrRef::Ref {
153 ref_path: "#/components/schemas/Cloned".to_string(),
154 };
155 let cloned = sor.clone();
156 assert_eq!(cloned.ref_name(), Some("Cloned"));
157 }
158
159 #[test]
160 fn ref_name_deeply_nested_path() {
161 let sor = SchemaOrRef::Ref {
162 ref_path: "a/b/c/d/e/DeepName".to_string(),
163 };
164 assert_eq!(sor.ref_name(), Some("DeepName"));
165 }
166
167 #[test]
168 fn from_trait_ref_variant() {
169 let schema = sekkei::Schema {
170 ref_path: Some("#/components/schemas/Baz".to_string()),
171 ..sekkei::Schema::default()
172 };
173 let sor = SchemaOrRef::from(&schema);
174 assert_eq!(sor.ref_name(), Some("Baz"));
175 }
176
177 #[test]
178 fn from_trait_schema_variant() {
179 let schema = sekkei::Schema::default();
180 let sor = SchemaOrRef::from(&schema);
181 assert!(matches!(sor, SchemaOrRef::Schema(_)));
182 }
183
184 #[test]
185 #[allow(deprecated)]
186 fn schema_or_ref_from_schema_ref_name_round_trip() {
187 let schema = sekkei::Schema {
188 ref_path: Some("#/components/schemas/RoundTrip".to_string()),
189 ..sekkei::Schema::default()
190 };
191 let sor = SchemaOrRef::from_schema(&schema);
192 assert_eq!(sor.ref_name(), Some("RoundTrip"));
193 }
194}