mwapi_responses 0.5.0

Automatically generate strict types for MediaWiki API responses
Documentation
#!/usr/bin/env python3
"""
Copyright (C) 2020-2021 Kunal Mehta <legoktm@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""
import json
from pathlib import Path
import subprocess

root = Path(__file__).parent
data_dir = root / "../mwapi_responses_derive/data"
test_dir = root / "tests"


def handle_file(data: dict):
    params = {
        "action": "query",
        data["mode"]: data["name"][6:],
    }
    name = f"query_{data['name'][6:]}"
    props = set([field["prop"] for field in data["fields"]])
    test = """
// Autogenerated by gen_tests.py
use mwapi_responses::prelude::*;

mod test_client;


"""
    counter = 0
    for prop in sorted(props):
        if "||" in prop:
            continue
        if prop == "=default":
            prop = "default"
        else:
            params[data["prop"]] = prop
        test += write_test(params, f"{name}_{prop}", counter, data["test_extra"])
        counter += 1
    fname = test_dir / f"{name}.rs"
    fname.write_text(test)
    print(f"Wrote {fname}")


def write_test(params: dict, name: str, counter: int, extra: dict) -> str:
    macro = "#[query("
    for key, val in params.items():
        macro += f'{key}="{val}",'
    macro += ")]"
    extra_rs = ""
    mut = "mut "
    for key, value in sorted(extra["params"].items()):
        extra_rs += f'params.push(("{key}", "{value}"));'
    if not extra_rs:
        mut = ""
    if name in extra.get("ignore", {}):
        ignore = "#[ignore] // " + extra["ignore"][name]
    else:
        ignore = ""
    # TODO: Use #[serde(deny_unknown_fields)]
    return f"""

{macro}
struct Response{counter};

{ignore}
#[tokio::test]
async fn {name}() {{
    let {mut}params = Response{counter}::params(){".to_vec()" if mut else ""};
    {extra_rs}
    let resp: Response{counter} = test_client::test({"&" if mut else ""}params).await.unwrap();
    assert_eq!(resp.items().len(), {extra['assert'].get('length', 10)});
    assert!({"!" if extra["assert"].get("continue", True) else ""}resp.continue_.is_empty());
}}

"""


def main():
    for fname in data_dir.iterdir():
        print(f"Generating test for {fname}")
        data = json.loads(fname.read_text())
        handle_file(data)
    subprocess.check_call(["cargo", "fmt"])


if __name__ == "__main__":
    main()