# Buggy code
def construct_rule(params):
    rule = []
    append_param(rule, params["protocol"], "-p", False)
    append_param(rule, params["source"], "-s", False)
    append_param(rule, params["destination"], "-d", False)
    append_param(rule, params["match"], "-m", True)
    append_param(rule, params["jump"], "-j", False)
    append_param(rule, params["to_destination"], "--to-destination", False)
    append_param(rule, params["to_source"], "--to-source", False)
    append_param(rule, params["goto"], "-g", False)
    append_param(rule, params["in_interface"], "-i", False)
    append_param(rule, params["out_interface"], "-o", False)
    append_param(rule, params["fragment"], "-f", False)
    append_param(rule, params["set_counters"], "-c", False)
    append_param(rule, params["source_port"], "--source-port", False)
    append_param(rule, params["destination_port"], "--destination-port", False)
    append_param(rule, params["to_ports"], "--to-ports", False)
    append_param(rule, params["set_dscp_mark"], "--set-dscp", False)
    append_param(rule, params["set_dscp_mark_class"], "--set-dscp-class", False)
    append_match(rule, params["comment"], "comment")
    append_param(rule, params["comment"], "--comment", False)
    if "conntrack" in params["match"]:
        append_csv(rule, params["ctstate"], "--ctstate")
    elif "state" in params["match"]:
        append_csv(rule, params["ctstate"], "--state")
    elif params["ctstate"]:
        append_match(rule, params["ctstate"], "conntrack")
        append_csv(rule, params["ctstate"], "--ctstate")
    else:
        return False
    append_match(rule, params["limit"] or params["limit_burst"], "limit")
    append_param(rule, params["limit"], "--limit", False)
    append_param(rule, params["limit_burst"], "--limit-burst", False)
    append_match(rule, params["uid_owner"], "owner")
    append_param(rule, params["uid_owner"], "--uid-owner", False)
    append_jump(rule, params["reject_with"], "REJECT")
    append_param(rule, params["reject_with"], "--reject-with", False)
    append_param(
        rule, params["icmp_type"], ICMP_TYPE_OPTIONS[params["ip_version"]], False
    )
    return rule


# Traceback (TypeError)
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: can only join an iterable
failed: [localhost] (item=({u'image': u'ubuntu/xenial/amd64', u'name': u'proxy'}, {u'lxd_port': u'443', u'wan_port': u'443'})) => {"failed": true, "item": [{"image": "ubuntu/xenial/amd64", "name": "proxy"}, {"lxd_port": "443", "wan_port": "443"}], "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_CbCfun/ansible_module_iptables.py\", line 576, in <module>\n    main()\n  File \"/tmp/ansible_CbCfun/ansible_module_iptables.py\", line 532, in main\n    rule=' '.join(construct_rule(module.params)),\nTypeError: can only join an iterable\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 0}
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: can only join an iterable
failed: [localhost] (item=({u'image': u'ubuntu/xenial/amd64', u'name': u'proxy'}, {u'lxd_port': u'80', u'wan_port': u'80'})) => {"failed": true, "item": [{"image": "ubuntu/xenial/amd64", "name": "proxy"}, {"lxd_port": "80", "wan_port": "80"}], "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_5esHLN/ansible_module_iptables.py\", line 576, in <module>\n    main()\n  File \"/tmp/ansible_5esHLN/ansible_module_iptables.py\", line 532, in main\n    rule=' '.join(construct_rule(module.params)),\nTypeError: can only join an iterable\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 0}
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: can only join an iterable
failed: [localhost] (item=({u'image': u'ubuntu/xenial/amd64', u'name': u'srv-1'}, {u'protocol': u'tcp', u'lxd_port': u'22', u'wan_port': u'22129'})) => {"failed": true, "item": [{"image": "ubuntu/xenial/amd64", "name": "japex"}, {"lxd_port": "22", "protocol": "tcp", "wan_port": "22129"}], "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_d989dF/ansible_module_iptables.py\", line 576, in <module>\n    main()\n  File \"/tmp/ansible_d989dF/ansible_module_iptables.py\", line 532, in main\n    rule=' '.join(construct_rule(module.params)),\nTypeError: can only join an iterable\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 0}

# Fixed code
def construct_rule(params):
    rule = []
    append_param(rule, params["protocol"], "-p", False)
    append_param(rule, params["source"], "-s", False)
    append_param(rule, params["destination"], "-d", False)
    append_param(rule, params["match"], "-m", True)
    append_param(rule, params["jump"], "-j", False)
    append_param(rule, params["to_destination"], "--to-destination", False)
    append_param(rule, params["to_source"], "--to-source", False)
    append_param(rule, params["goto"], "-g", False)
    append_param(rule, params["in_interface"], "-i", False)
    append_param(rule, params["out_interface"], "-o", False)
    append_param(rule, params["fragment"], "-f", False)
    append_param(rule, params["set_counters"], "-c", False)
    append_param(rule, params["source_port"], "--source-port", False)
    append_param(rule, params["destination_port"], "--destination-port", False)
    append_param(rule, params["to_ports"], "--to-ports", False)
    append_param(rule, params["set_dscp_mark"], "--set-dscp", False)
    append_param(rule, params["set_dscp_mark_class"], "--set-dscp-class", False)
    append_match(rule, params["comment"], "comment")
    append_param(rule, params["comment"], "--comment", False)
    if "conntrack" in params["match"]:
        append_csv(rule, params["ctstate"], "--ctstate")
    elif "state" in params["match"]:
        append_csv(rule, params["ctstate"], "--state")
    elif params["ctstate"]:
        append_match(rule, params["ctstate"], "conntrack")
        append_csv(rule, params["ctstate"], "--ctstate")
    append_match(rule, params["limit"] or params["limit_burst"], "limit")
    append_param(rule, params["limit"], "--limit", False)
    append_param(rule, params["limit_burst"], "--limit-burst", False)
    append_match(rule, params["uid_owner"], "owner")
    append_param(rule, params["uid_owner"], "--uid-owner", False)
    append_jump(rule, params["reject_with"], "REJECT")
    append_param(rule, params["reject_with"], "--reject-with", False)
    append_param(
        rule, params["icmp_type"], ICMP_TYPE_OPTIONS[params["ip_version"]], False
    )
    return rule