haste_fhir_ops_derive/
lib.rs1use haste_fhir_model::r4::generated::resources::ResourceType;
2use proc_macro::TokenStream;
3use quote::{ToTokens, format_ident, quote};
4use syn::{
5 parse_macro_input, Attribute, Data, DeriveInput, Expr, Field, Fields, Lit, Meta, PathArguments, PathSegment, Type
6};
7
8fn get_attribute_value(attrs: &[Attribute], attribute: &str) -> Option<String> {
9 attrs.iter().find_map(|attr| match &attr.meta {
10 Meta::NameValue(name_value) => {
11 if name_value.path.is_ident(attribute) {
12 match &name_value.value {
13 Expr::Lit(lit) => match &lit.lit {
14 Lit::Str(lit) => Some(lit.value()),
15 _ => panic!("Expected a string literal"),
16 },
17 _ => panic!("Expected a string literal"),
18 }
19 } else {
20 None
21 }
22 }
23 _ => None,
24 })
25}
26
27fn get_parameter_name(field: &Field) -> String {
28 if let Some(rename) = get_attribute_value(&field.attrs, "parameter_rename") { rename } else { field.ident.as_ref().unwrap().to_string() }
29}
30
31fn is_nested_parameter(attrs: &[Attribute]) -> bool {
32 attrs
33 .iter()
34 .any(|attr| attr.path().is_ident("parameter_nested"))
35}
36
37fn determine_is_vector(field: &Field) -> bool {
38 let inner_type = get_optional_type(field);
39
40 inner_type.ident == format_ident!("Vec")
41}
42
43fn _inner_type(segment: &PathSegment, ignore_cases: Vec<String>) -> PathSegment {
44 if ignore_cases.contains(&segment.ident.to_string()) {
45 match &segment.arguments {
46 PathArguments::AngleBracketed(args) => {
47 if let Some(syn::GenericArgument::Type(Type::Path(inner_path))) = args.args.first()
48 {
49 let k = inner_path.path.segments.first().unwrap();
50 _inner_type(k, ignore_cases)
51 } else {
52 panic!("invalid");
53 }
54 }
55 _ => panic!("invalid"),
56 }
57 } else {
58 segment.clone()
59 }
60}
61
62fn inner_type(field: &Field) -> PathSegment {
64 match &field.ty {
65 Type::Path(path) => {
66 let type_ = path.path.segments.first().unwrap();
67 _inner_type(type_, vec!["Option".to_string(), "Vec".to_string()])
68 }
69 _ => panic!("Unsupported field type for serialization"),
70 }
71}
72
73fn get_optional_type(field: &Field) -> PathSegment {
75 match &field.ty {
76 Type::Path(path) => {
77 let type_ = path.path.segments.first().unwrap();
78 _inner_type(type_, vec!["Option".to_string()])
79 }
80 _ => panic!("Unsupported field type for serialization"),
81 }
82}
83
84fn is_optional(field: &Field) -> bool {
85 match &field.ty {
86 Type::Path(path) => {
87 let type_ = path.path.segments.first().unwrap();
88 type_.ident == format_ident!("Option")
89 }
90 _ => panic!("Unsupported field type for serialization"),
91 }
92}
93
94
95fn is_resource_type(field: &Field) -> bool {
96 let field_type = inner_type(field).ident;
97
98 let res = ResourceType::try_from(field_type.to_string());
99 if let Ok(_) = res {
100 return true;
101 } else {
102 return false;
103 }
104}
105
106fn build_return_value(fields: &Fields) -> proc_macro2::TokenStream {
107
108 let field_setters = fields.iter().map(|field| {
109 let optional = is_optional(field);
110 let field = field.ident.as_ref().unwrap();
111 let field_name = field.to_string();
112
113 if optional {
114 quote!{
115 #field: #field
116 }
117 } else {
118 quote!{
119 #field: #field.ok_or_else(||
120 OperationOutcomeError::error(
121 haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Field '{}' is required.", stringify!(#field_name))))?
122 }
123 }
124
125 });
126
127 quote! {
128 Ok(Self {
129 #(#field_setters),*
130 })
131 }
132}
133
134#[proc_macro_derive(ToParameters, attributes(parameter_rename, parameter_nested))]
135pub fn haste_to_parameters(input: TokenStream) -> TokenStream {
136 let input = parse_macro_input!(input as DeriveInput);
138
139 match input.data {
140 Data::Struct(data) => {
141 let struct_name = input.ident;
142 let parameters_name = format_ident!("parameters");
143 let var_name = format_ident!("s");
144
145 let to_parameters = data.fields.iter().map(|field| {
146 let field_name = field.ident.as_ref().unwrap();
147 let value_type = inner_type(field);
148 let expected_parameter_name = get_parameter_name(field);
149 let is_optional = is_optional(field);
150 let tmp_name = format_ident!("tmp");
151
152 let mut as_param = if is_nested_parameter(&field.attrs) {
153 quote!{
154 #parameters_name.push(ParametersParameter {
155 name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
156 part: Some(#tmp_name.into()),
157 ..Default::default()
158 });
159 }
160 }else if value_type.ident == format_ident!("Resource") {
161 quote!{
162 #parameters_name.push(ParametersParameter {
163 name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
164 resource: Some(Box::new(#tmp_name)),
165 ..Default::default()
166 });
167 }
168 }else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
169 quote! {
170 #parameters_name.push(ParametersParameter {
171 name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
172 value: Some(#tmp_name),
173 ..Default::default()
174 });
175 }
176 }
177 else if is_resource_type(field) {
178 quote!{
179 #parameters_name.push(ParametersParameter {
180 name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
181 resource: Some(Box::new(Resource::#value_type(#tmp_name))),
182 ..Default::default()
183 });
184 }
185
186 } else {
187 let removed_fhir = value_type.ident.to_string().replacen("FHIR", "", 1);
189 let parameter_value_type = format_ident!("{}", removed_fhir);
190
191 quote! {
192 #parameters_name.push(ParametersParameter {
193 name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
194 value: Some(haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(Box::new(#tmp_name))),
195 ..Default::default()
196 });
197 }
198 };
199
200 if determine_is_vector(field) {
201 as_param = quote! {
202 for #tmp_name in #tmp_name.into_iter() {
203 #as_param
204 }
205 };
206 }
207
208 if is_optional {
209 quote! {
210 if let Some(#tmp_name) = #var_name.#field_name {
211 #as_param
212 }
213 }
214 }
215 else {
216 quote! {
217 let #tmp_name = #var_name.#field_name;
218 #as_param
219 }
220 }
221 });
222
223 let try_from_code = quote! {
224 impl From<#struct_name> for Vec<ParametersParameter> {
225 fn from(#var_name: #struct_name) -> Self {
226 let mut #parameters_name = vec![];
227 #(#to_parameters)*
228 #parameters_name
229 }
230 }
231 };
232
233 try_from_code.into()
235
236 }
237 _ => panic!("From parameter deriviation is only supported for structs."),
238 }
239}
240
241
242#[proc_macro_derive(FromParameters, attributes(parameter_rename, parameter_nested))]
243pub fn haste_from_parameters(input: TokenStream) -> TokenStream {
244 let input = parse_macro_input!(input as DeriveInput);
246
247 match input.data {
248 Data::Struct(data) => {
249 let struct_name = input.ident;
250 let parameters_name = format_ident!("parameters");
251 let current_parameter = format_ident!("param");
252
253 let declare_fields = data.fields.iter().map(|field| {
255 let field_name = field.ident.as_ref().unwrap();
256 let field_type_token = get_optional_type(field).to_token_stream();
257
258 quote! {
259 let mut #field_name: Option<#field_type_token> = None;
260 }
261 });
262
263 let set_fields = data.fields.iter().map(|field| {
264 let field_name = field.ident.as_ref().unwrap();
265 let is_vector = determine_is_vector(field);
266 let value_type = inner_type(field);
267 let expected_parameter_name = get_parameter_name(field);
268
269 let get_value_from_param = if is_nested_parameter(&field.attrs) {
270 quote!{
271 #value_type::try_from(#current_parameter.part.unwrap_or_default()).map(|v| Some(v))
272 }
273 }else if value_type.ident == format_ident!("Resource") {
274 quote!{
275 Ok(#current_parameter.resource.map(|r| *r))
276 }
277 }else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
278 quote! {
279 Ok(#current_parameter.value)
280 }
281 }
282 else if is_resource_type(field) {
283 quote! {
284 if let Some(Resource::#value_type(resource)) = #current_parameter.resource.map(|r| *r) {
285 Ok(Some(resource))
286 } else {
287 return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' does not contain correct value type.", #expected_parameter_name)));
288 }
289 }
290 } else {
291 let removed_fhir = value_type.ident.to_string().replacen("FHIR", "", 1);
293 let parameter_value_type = format_ident!("{}", removed_fhir);
294
295 quote! {
296 if let Some(haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(value)) = #current_parameter.value {
297 Ok(Some(*value))
298 } else {
299 return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' does not contain correct value type.", #expected_parameter_name)));
300 }
301 }
302 };
303
304 let setter = if is_vector {
305 quote! {
306 let tmp_value: Result<_, OperationOutcomeError> = #get_value_from_param;
307 if let Some(tmp_value) = tmp_value? {
308 if let Some(tmp_array) = #field_name.as_mut(){
309 tmp_array.push(tmp_value);
310 } else {
311 #field_name = Some(vec![tmp_value]);
312 }
313 }
314 }
315 } else {
316 quote! {
317 if #field_name.is_some(){
318 return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' is not allowed to be repeated.", #expected_parameter_name)));
319 }
320 let tmp_value: Result<_, OperationOutcomeError> = #get_value_from_param;
321 #field_name = tmp_value?;
322 }
323 };
324
325 quote!{
326 Some(#expected_parameter_name) => {
327 #setter
328 }
329 }
330 });
331
332 let return_value = build_return_value(&data.fields);
333
334 let try_from_code = quote! {
335 impl TryFrom<Vec<ParametersParameter>> for #struct_name {
336 type Error = OperationOutcomeError;
337 fn try_from(#parameters_name: Vec<ParametersParameter>) -> Result<Self, Self::Error> {
338 #(#declare_fields)*
339
340 for #current_parameter in #parameters_name {
341 match #current_parameter.name.value.as_ref().map(|v| v.as_str()) {
342 #(#set_fields),*
343 Some(k) => {
344 return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' is not allowed.", k)));
345 },
346 None => {
347 return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter must have a name on it")));
348 }
349 }
350 }
351
352 #return_value
353 }
354 }
355 };
356
357 try_from_code.into()
358 }
359 _ => panic!("From parameter deriviation is only supported for structs."),
360 }
361}