fluence_sdk_wit/parse_macro_input/
item_record.rs

1/*
2 * Copyright 2020 Fluence Labs Limited
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use super::ParseMacroInput;
18use crate::ast_types;
19use crate::ast_types::AstRecordField;
20use crate::ast_types::AstRecordFields;
21use crate::ast_types::FCEAst;
22use crate::syn_error;
23use crate::parsed_type::ParsedType;
24
25use syn::Result;
26use syn::spanned::Spanned;
27
28impl ParseMacroInput for syn::ItemStruct {
29    fn parse_macro_input(self) -> Result<FCEAst> {
30        check_record(&self)?;
31
32        let fields = match &self.fields {
33            syn::Fields::Named(named_fields) => &named_fields.named,
34            _ => return syn_error!(self.span(), "only named fields are allowed in structs"),
35        };
36
37        let fields = fields_into_ast(fields)?;
38        let fields = AstRecordFields::Named(fields);
39
40        let name = self.ident.to_string();
41        let ast_record_item = ast_types::AstRecord {
42            name,
43            fields,
44            original: self,
45        };
46
47        Ok(FCEAst::Record(ast_record_item))
48    }
49}
50
51fn check_record(record: &syn::ItemStruct) -> Result<()> {
52    if record.generics.lt_token.is_some()
53        || record.generics.gt_token.is_some()
54        || record.generics.where_clause.is_some()
55    {
56        return syn_error!(
57            record.span(),
58            "#[fce] couldn't be applied to a struct with generics or lifetimes"
59        );
60    }
61
62    Ok(())
63}
64
65fn fields_into_ast(
66    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
67) -> Result<Vec<AstRecordField>> {
68    fields
69        .iter()
70        .map(|field| {
71            check_field(field)?;
72            let name = field.ident.as_ref().map(|ident| {
73                ident
74                    .to_string()
75                    .split(' ')
76                    .last()
77                    .unwrap_or_default()
78                    .to_string()
79            });
80            let ty = ParsedType::from_type(&field.ty)?;
81
82            let record_field = AstRecordField { name, ty };
83            Ok(record_field)
84        })
85        .collect::<Result<Vec<_>>>()
86}
87
88/// Check that record fields satisfy the following requirements:
89///  - all fields must be public
90///  - field must have only doc attributes
91fn check_field(field: &syn::Field) -> Result<()> {
92    match field.vis {
93        syn::Visibility::Public(_) => {}
94        _ => {
95            return syn_error!(
96                field.span(),
97                "#[fce] could be applied only to struct with all public fields"
98            )
99        }
100    };
101
102    const DOC_ATTR_NAME: &str = "doc";
103
104    // Check that all attributes are doc attributes
105    let is_all_attrs_public = field.attrs.iter().all(|attr| {
106        let meta = match attr.parse_meta() {
107            Ok(meta) => meta,
108            Err(_) => return false,
109        };
110        meta.path().is_ident(DOC_ATTR_NAME)
111    });
112
113    if !is_all_attrs_public {
114        return syn_error!(field.span(), "field attributes isn't allowed");
115    }
116
117    Ok(())
118}