1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! This crate is supposed to support [`json_schema_test_suite`](https://crates.io/crates/json_schema_test_suite)
//! by exporting `json_schema_test_suite` procedural macro.
//!
//! Please refer to [`json-schema-test-suite-proc-macro` docs](https://docs.rs/json-schema-test-suite) for more informaton.
#![warn(
    clippy::cast_possible_truncation,
    clippy::doc_markdown,
    clippy::explicit_iter_loop,
    clippy::match_same_arms,
    clippy::needless_borrow,
    clippy::needless_pass_by_value,
    clippy::option_map_unwrap_or,
    clippy::option_map_unwrap_or_else,
    clippy::option_unwrap_used,
    clippy::pedantic,
    clippy::print_stdout,
    clippy::redundant_closure,
    clippy::result_map_unwrap_or_else,
    clippy::result_unwrap_used,
    clippy::trivially_copy_pass_by_ref,
    missing_debug_implementations,
    missing_docs,
    trivial_casts,
    unreachable_pub,
    unsafe_code,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
    unused_results,
    variant_size_differences
)]
mod attribute_config;
mod mockito_mocks;
mod test_case;

use json_schema_test_suite_test_case::TestCase;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use regex::Regex;
use syn::{parse_macro_input, Ident, ItemFn};

fn test_token_stream(tests_to_ignore_regex: &[Regex], wrapped_test_ident: &Ident, test: &TestCase) -> TokenStream2 {
    let name = Ident::new(&test.name, Span::call_site());

    let maybe_ignore = if tests_to_ignore_regex.iter().any(|regex| regex.is_match(&test.name)) {
        quote! { #[ignore] }
    } else {
        quote! {}
    };

    let wrapped_test_case = test_case::WrappedTestCase::from(test);
    quote! {
        #[test]
        #maybe_ignore
        fn #name() {
            setup_mocks();

            super::#wrapped_test_ident(
                &mockito::server_address().to_string(),
                #wrapped_test_case,
            );
        }
    }
}

/// Procedural macro that allows a test to be executed for all the configurations defined
/// by [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
///
/// The `proc_macro_attribute` should be used on a function with the current signature
/// ```rust
/// # use json_schema_test_suite_test_case::TestCase;
/// fn my_simple_test(
///     // address of the HTTP server providing the remote files of JSON-Schema-Test-Suite. The format will be: `hostname:port`
///     // This parameter is passed because by starting a mock server we might not start it into `localhost:1234` as expected by JSON-Schema-Test-Suite
///     server_address: &str,
///     // Representation of the test case (includes draft_version, descriptions, schema, instance, expected_valid)
///     test_case: TestCase,
/// ) {
///     // TODO: Add here your testing logic
/// }
/// ```
#[proc_macro_attribute]
pub fn json_schema_test_suite(attr: TokenStream, item: TokenStream) -> TokenStream {
    let proc_macro_attributes = parse_macro_input!(attr as attribute_config::AttrConfig);
    let item_fn = parse_macro_input!(item as ItemFn);

    let original_function_ident = &item_fn.sig.ident;
    let tests_token_stream: Vec<TokenStream2> = test_case::load(
        &proc_macro_attributes.json_schema_test_suite_path,
        &proc_macro_attributes.draft_folder,
    )
    .iter()
    .map(|test| test_token_stream(&proc_macro_attributes.tests_to_exclude_regex, original_function_ident, test))
    .collect();

    let setup_mockito_mocks_token_stream = mockito_mocks::setup(&proc_macro_attributes.json_schema_test_suite_path);

    let mod_name = format_ident!("{}_{}", original_function_ident, proc_macro_attributes.draft_folder);

    let output = quote! {
        #item_fn

        mod #mod_name {
            lazy_static::lazy_static! {
                static ref MOCKS: Vec<mockito::Mock> = vec![
                    #(#setup_mockito_mocks_token_stream),*
                ];
            }

            fn setup_mocks() {
                // Dereference to ensure that lazy_static actually invokes the mocks creation
                let _ = *MOCKS;
            }

            #(#tests_token_stream)*
        }
    };
    output.into()
}