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