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
121
122
123
124
125
// Copyright 2022 rust-ipfs-api Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! Define which IPFS (Kubo) Docker images the Rust library will be tested against.
//!
//! # Examples
//!
//! ```rust
//! use ipfs_api_versions::test_current_image;
//!
//! #[test_current_image]
//! fn test_foo(image_name: &str, image_tag: &str) {
//!     // ...test implementation to run against only the latest image
//! }
//! ```
//!
//! ```rust
//! use ipfs_api_versions::test_supported_images;
//!
//! #[test_supported_images]
//! fn test_bar(image_name: &str, image_tag: &str) {
//!     // ...test implementation to run against all supported images
//! }
//! ```

use proc_macro::TokenStream as CompilerTokenStream;
use quote::{quote, quote_spanned};

/// Docker images of IPFS daemon versions supported by this library, in ascending order.
fn supported() -> Vec<(String, String)> {
    let source = [
        ("ipfs/go-ipfs", "v0.7.0"),
        ("ipfs/go-ipfs", "v0.8.0"),
        ("ipfs/go-ipfs", "v0.9.1"),
        ("ipfs/go-ipfs", "v0.10.0"),
        ("ipfs/go-ipfs", "v0.11.1"),
        ("ipfs/go-ipfs", "v0.12.2"),
        ("ipfs/go-ipfs", "v0.13.0"),
        ("ipfs/kubo", "v0.14.0"),
        ("ipfs/kubo", "v0.15.0"),
        ("ipfs/kubo", "v0.16.0"),
        ("ipfs/kubo", "v0.17.0"),
    ];

    source
        .into_iter()
        .map(|(i, t)| (i.into(), t.into()))
        .collect()
}

/// Docker image of most recent supported IPFS daemon.
fn current() -> (String, String) {
    supported().into_iter().last().unwrap()
}

fn image_test_case(image_name: &str, image_tag: &str) -> proc_macro2::TokenStream {
    quote! {
        #[test_case::test_case(#image_name, #image_tag)]
    }
}

fn unexpected_meta(meta: CompilerTokenStream) -> Option<CompilerTokenStream> {
    let m2: proc_macro2::TokenStream = meta.into();

    if let Some(m) = m2.into_iter().next() {
        let result = quote_spanned! { m.span() =>
            compile_error!("Macro does not expect any arguments.");
        };

        Some(result.into())
    } else {
        None
    }
}

#[proc_macro_attribute]
pub fn test_current_image(
    meta: CompilerTokenStream,
    input: CompilerTokenStream,
) -> CompilerTokenStream {
    if let Some(err) = unexpected_meta(meta) {
        err
    } else {
        let (image_name, image_tag) = current();

        let tokens = vec![image_test_case(&image_name, &image_tag), input.into()];

        let result = quote! {
            #(#tokens)*
        };

        result.into()
    }
}

#[proc_macro_attribute]
pub fn test_supported_images(
    meta: CompilerTokenStream,
    input: CompilerTokenStream,
) -> CompilerTokenStream {
    if let Some(err) = unexpected_meta(meta) {
        err
    } else {
        let mut tokens: Vec<_> = supported()
            .iter()
            .map(|(image_name, image_tag)| {
                quote! {
                    #[test_case::test_case(#image_name, #image_tag)]
                }
            })
            .collect();

        tokens.push(input.into());

        let result = quote! {
            #(#tokens)*
        };

        result.into()
    }
}