sails-client-gen-v2 1.0.0-beta.5

Rust client generator for the Sails framework
Documentation
use crate::{
    ctor_generators::*, helpers::generate_doc_comments, service_generators::*, type_generators::*,
};
use convert_case::{Case, Casing};
use genco::prelude::*;
use rust::Tokens;
use sails_idl_parser_v2::ast;
use sails_idl_parser_v2::visitor::Visitor;
use std::collections::{HashMap, HashSet};

pub(crate) struct RootGenerator<'ast> {
    tokens: Tokens,
    service_impl_tokens: Tokens,
    service_trait_tokens: Tokens,
    program_meta_tokens: Tokens,
    program_name: Option<&'ast str>,
    mocks_feature_name: Option<&'ast str>,
    sails_path: &'ast str,
    external_types: HashMap<&'ast str, &'ast str>,
    no_derive_traits: bool,
    program_types: HashSet<&'ast str>,
}

impl<'ast> RootGenerator<'ast> {
    pub(crate) fn new(
        mocks_feature_name: Option<&'ast str>,
        sails_path: &'ast str,
        external_types: HashMap<&'ast str, &'ast str>,
        no_derive_traits: bool,
    ) -> Self {
        Self {
            tokens: Tokens::new(),
            service_impl_tokens: Tokens::new(),
            service_trait_tokens: Tokens::new(),
            program_meta_tokens: Tokens::new(),
            program_name: None,
            mocks_feature_name,
            sails_path,
            external_types,
            no_derive_traits,
            program_types: HashSet::new(),
        }
    }

    pub(crate) fn finalize(self, with_no_std: bool) -> String {
        let mut tokens = if let Some(mocks_feature_name) = self.mocks_feature_name {
            quote! {
                $['\n']
                #[cfg(feature = $(quoted(mocks_feature_name)))]
                #[cfg(not(target_arch = "wasm32"))]
                extern crate std;
            }
        } else {
            Tokens::new()
        };

        quote_in! { tokens =>
            #[allow(unused_imports)]
            use $(self.sails_path)::{client::*, collections::*, prelude::*};
        };

        for (&name, &path) in &self.external_types {
            quote_in! { tokens =>
                #[allow(unused_imports)]
                use $path as $name;
            };
        }

        if let Some(program_name) = self.program_name {
            quote_in! { tokens =>
                pub struct $(program_name)Program;

                impl $(program_name)Program {
                    $(self.program_meta_tokens)
                }

                impl $(self.sails_path)::client::Program for  $(program_name)Program {}

                pub trait $program_name {
                    type Env: $(self.sails_path)::client::GearEnv;
                    $(self.service_trait_tokens)
                }

                impl<E: $(self.sails_path)::client::GearEnv> $program_name for $(self.sails_path)::client::Actor<$(program_name)Program, E> {
                    type Env = E;
                    $(self.service_impl_tokens)
                }
            };
        }

        tokens.extend(self.tokens);

        let mut result = tokens.to_file_string().unwrap();

        if with_no_std {
            result.insert_str(
                0,
                "// Code generated by sails-client-gen-v2. DO NOT EDIT.\n#![no_std]\n\n",
            );
        } else {
            result.insert_str(
                0,
                "// Code generated by sails-client-gen-v2. DO NOT EDIT.\n",
            );
        }

        result
    }
}

impl<'ast> Visitor<'ast> for RootGenerator<'ast> {
    fn visit_program_unit(&mut self, program: &'ast ast::ProgramUnit) {
        self.program_name = Some(&program.name);

        let mut ctor_gen = CtorGenerator::new(&program.name, self.sails_path);
        ctor_gen.visit_program_unit(program);
        self.tokens.extend(ctor_gen.finalize());

        sails_idl_parser_v2::visitor::accept_program_unit(program, self);
    }

    fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) {
        let mut client_gen = ServiceGenerator::new(
            &service.name.name,
            self.sails_path,
            &self.external_types,
            self.mocks_feature_name,
            service
                .name
                .interface_id
                .expect("Service must have an interface ID"),
            self.no_derive_traits,
        );
        client_gen.visit_service_unit(service);
        self.tokens.extend(client_gen.finalize());
    }

    fn visit_type(&mut self, t: &'ast ast::Type) {
        self.program_types.insert(&t.name);
        if self.external_types.contains_key(t.name.as_str()) {
            return;
        }
        let mut type_gen =
            TopLevelTypeGenerator::new(&t.name, self.sails_path, self.no_derive_traits);
        type_gen.visit_type(t);
        self.tokens.extend(type_gen.finalize());
    }

    fn visit_service_expo(&mut self, service_item: &'ast ast::ServiceExpo) {
        let service_route = service_item
            .route
            .as_ref()
            .unwrap_or(&service_item.name.name);
        let method_name = service_route.to_case(Case::Snake);
        let name_pascal_case = service_item.name.name.to_case(Case::Pascal);
        let name_snake_case = service_item.name.name.to_case(Case::Snake);
        let program_name = self.program_name.unwrap();
        let program_program_name = format!("{program_name}Program");

        generate_doc_comments(&mut self.service_trait_tokens, &service_item.docs);

        let route_id_const_name = format!("ROUTE_ID_{}", service_route.to_case(Case::UpperSnake));

        quote_in!(self.program_meta_tokens =>
             pub const $(&route_id_const_name): u8 = $(service_item.route_idx);
        );

        quote_in!(self.service_trait_tokens =>
            $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(&name_snake_case)::$(&name_pascal_case)Impl, Self::Env>;
        );

        quote_in!(self.service_impl_tokens =>
            $['\r'] fn $(&method_name)(&self) -> $(self.sails_path)::client::Service<$(&name_snake_case)::$(&name_pascal_case)Impl, Self::Env> {
                self.service($(&program_program_name)::$(&route_id_const_name))
            }
        );
    }
}