#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args

# try importing JSON and YAML modules
%try-module json
%define NoJson
%endtry
%try-module yaml
%define NoYaml
%endtry

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/Mime.qm
%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/HttpServerUtil.qm
%requires ../../../../qlib/RestSchemaValidator.qm
%requires ../../../../qlib/Swagger.qm
%requires ../../../../qlib/ConnectionProvider
%requires ../../../../qlib/RestClient.qm

%exec-class SwaggerTest

string sub my_make_json(any v) {
%ifndef NoJson
    return make_json(v);
%else
    return "";
%endif
}

string sub my_make_yaml(any v) {
%ifndef NoYaml
    return make_yaml(v);
%else
    return "";
%endif
}

class HttpTestServer {
    public {
        int port = 0;
        string body;
    }

    private {
        Counter end();
        string resp;
        Socket s();
    }

    constructor(string r) {
        resp = r;

        restart();
    }

    done() {
        end.dec();
    }

    restart() {
        end.inc();
        # start a fake server
        if (s.bind(port, True))
            throw "BIND-ERROR", strerror();

        if (s.listen())
            throw "LISTEN-ERROR", strerror();

        if (!port) {
            port = s.getSocketInfo().port;
        }
        background startListener(s);
    }

    private startListener(Socket s) {
        Socket s1 = s.accept();
        hash h = s1.readHTTPHeader();
        if (h."content-length")
            body = s1.recv(h."content-length");
        else if (h."transfer-encoding" == "chunked")
            body = s1.readHTTPChunkedBody().body;
        s1.send(resp);
        end.waitForZero();
        s.close();
        s.shutdown();
    }

    string getBody() {
        return body;
    }
}

public class SwaggerTest inherits QUnit::Test {
    private:internal {
        const ValidInfoObject = {
            "title": "t",
            "version": "5"
        };
        const ValidPathsObject = {
            "/api/smtg": {
                "parameters": ( {"$ref": "#/parameters/a"}, )
            },
        };
        const ValidSwaggerSchema = {
            "swagger": "2.0",
            "info": ValidInfoObject,
            "paths": ValidPathsObject,
            "parameters": (
                "a": (
                    "name": "test",
                    "in": "query",
                    "description": "desc",
                    "required": False,
                    "type": "string",
                ),
                "enum": (
                    "name": "enum",
                    "in": "body",
                    "description": "desc",
                    "required": False,
                    "schema": (
                        "type": "integer",
                        "enum": (1, 2, 3),
                    ),
                ),
                "int": (
                    "name": "int",
                    "in": "query",
                    "description": "desc",
                    "required": False,
                    "type": "integer",
                ),
                "date": (
                    "name": "date",
                    "in": "query",
                    "description": "desc",
                    "required": False,
                    "type": "string",
                    "format": "date",
                ),
                "byte": (
                    "name": "byte",
                    "in": "query",
                    "description": "desc",
                    "required": False,
                    "type": "string",
                    "format": "byte",
                ),
                "string": (
                    "name": "string",
                    "in": "query",
                    "description": "desc",
                    "required": False,
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 5,
                    "enum": ("abc", "def", "ghi"),
                ),
            ),
        };

        const ValidInfoObjectJSON = my_make_json(ValidInfoObject);
        #const ValidPathsObjectJSON = my_make_json(ValidPathsObject);
        const ValidSwaggerSchemaJSON = my_make_json(ValidSwaggerSchema);

        const ValidInfoObjectYAML = my_make_yaml(ValidInfoObject);
        #const ValidPathsObjectYAML = my_make_yaml(ValidPathsObject);
        const ValidSwaggerSchemaYAML = my_make_yaml(ValidSwaggerSchema);

        code loadjson = bool sub (string str) {
            return Swagger::SwaggerLoader::fromString(str, True).val();
        };
        code loadyaml = bool sub (string str) {
            return Swagger::SwaggerLoader::fromString(str, False).val();
        };

        const OtherJsonErr = '{"type": "object", "properties": {"name": {"type": "string"}}}';
        const OtherJsonOk = '{"info": {"type": "object", "properties": {"name": {"type": "string"}}}}';

        const OtherYamlErr = '{type: object, properties: {name: {type: string}}}';
        const OtherYamlOk = '{info: {type: object, properties: {name: {type: string}}}}';

        const JsonRequestMessages = (
            "/pet": (
                "post": '{
  "id": 0,
  "category": {
    "id": 0,
    "name": "string"
  },
  "name": "doggie",
  "photoUrls": [
    "string"
  ],
  "tags": [
    {
      "id": 0,
      "name": "string"
    }
  ],
  "status": "available"
}',
                "put": '{
  "id": 0,
  "category": {
    "id": 0,
    "name": "string"
  },
  "name": "doggie",
  "photoUrls": [
    "string"
  ],
  "tags": [
    {
      "id": 0,
      "name": "string"
    }
  ],
  "status": "available"
}',
            ),
            "/pet/findByStatus?status=available": (
                "get": NOTHING,
            ),
            "/store/order": (
                "post": '{
  "id": 0,
  "petId": 0,
  "quantity": 0,
  "shipDate": "2017-09-03T14:16:20.924Z",
  "status": "placed",
  "complete": false
}',
            ),
        );

        const JsonResponseMessages = (
            "/store/inventory": (
                "get": (
                    200: "{
  \"1\": 1,
  \"3\": 1,
  \"111\": 1,
  \"11111\": 1,
  \"12312\": 1,
  \"21212\": 1,
  \"32432fsfdsfesfsaffwr3432432fdsfsfsff324324323242qafwfwsfwsfefew4324324324sdfdsafsfsafsfsfw324324324123dfsafdsf\": 1,
  \"unavailable\": 9,
  \"pending123\": 2,
  \"bbbbbb\": 1,
  \"werewrw\": 1,
  \"олл\": 1,
  \"xxxxxxxx\": 2,
  \"рор\": 1,
  \"ewrwerwe\": 1,
  \"availableeeeeeee\": 1,
  \"рот\": 1,
  \"Available\": 3323,
  \"цукуцк\": 1,
  \"мдфв\": 1,
  \"Vicious\": 1,
  \"Gð©A\\\u0011.\": 1,
  \"pata\": 10,
  \"completed\": 4,
  \"одб\": 1,
  \"bzdbznzdn\": 1,
  \"йуйц\": 1,
  \"ghthg\": 1,
  \"dfdgdfh\": 1,
  \"qweqwe\": 2,
  \"ertewer\": 1,
  \"уйцуйц\": 1,
  \"уцкцу\": 1,
  \"status\": 21,
  \"rr\": 1,
  \"рото\": 1,
  \"dead\": 1,
  \"stringgg\": 23,
  \"cute\": 1,
  \"sover\": 1,
  \"notavailable\": 2,
  \"пот\": 1,
  \"werwer\": 2,
  \"taken\": 1,
  \"fth\": 1,
  \"available333\": 3,
  \"available2222\": 3,
  \"укаука\": 1,
  \"wrwerw\": 1,
  \"salt\": 4,
  \"available2\": 1,
  \"born\": 1,
  \"qweq\": 1,
  \"available1\": 2,
  \"hhh\": 1,
  \"aaaaaa\": 1,
  \"уцкцук\": 2,
  \"ronaldo\": 1,
  \"vbsdbsdbsdbsd\": 2,
  \"vlad\": 3,
  \"rwetert\": 1,
  \"retrete\": 1,
  \"s\": 1,
  \"|\u001cf\bgOS@D\": 1,
  \"енн\": 2,
  \"available\": 13335,
  \"new avlbl\": 1,
  \"werwe\": 1,
  \"addedascanine\": 28,
  \"ывыаыв\": 1,
  \"available \": 1,
  \"мдывапа\": 1,
  \"йцуйцу\": 3,
  \"проо\": 2,
  \"цукцу\": 2,
  \"jhjhjhj\": 1,
  \"test\": 5,
  \"йцу\": 3,
  \"xxxxx\": 2,
  \"done\": 3,
  \"nnn\": 1,
  \"escaped\": 1,
  \"еукеу\": 1,
  \"\u0000\u0001\u00104~\u0012Ý\u0006\u0019\u000fN\": 1,
  \"vvv\": 1,
  \"true\": 1,
  \"retertrey\": 1,
  \"werr\": 1,
  \"/¤<\\4uBRµÁÄ\": 1,
  \"updated\": 1,
  \"erwerwe\": 1,
  \"UNavailable\": 1,
  \"string\": 67283,
  \"sail\": 1,
  \"eee\": 2,
  \"avai12lable\": 2,
  \"foo\": 1,
  \"pending\": 324,
  \"available;pending;sold\": 4,
  \"avale\": 1,
  \"Sold\": 4,
  \"not available\": 1,
  \"7g5C\f`l\": 1,
  \"passed\": 1,
  \"msvc\": 1,
  \"gogo\": 1,
  \"sold\": 356,
  \"false\": 2,
  \"\\\"Qw\": 1,
  \"now available\": 1,
  \"rtyr\": 1,
  \"ffhh\": 2,
  \"availableaaa\": 2,
  \"SOLD\": 1,
  \"Single\": 1,
  \"3333available\": 1,
  \"wrew\": 1,
  \"\u0019!\b¬í\u001cY'\": 1,
  \"availble\": 2,
  \"verybig\": 1
}",
                ),
            ),
        );
    }

    constructor() : Test("SwaggerTest", "1.0") {
        addTestCase("Test loading from files", \testFiles());
        addTestCase("Test SwaggerSchema", \testSwaggerSchema());
        addTestCase("Test InfoObject", \testInfoObject());
        addTestCase("Test ContactObject", \testContactObject());
        addTestCase("Test LicenseObject", \testLicenseObject());
        addTestCase("Test PathsObject", \testPathsObject());
        addTestCase("Test PathItemObject", \testPathItemObject());
        addTestCase("Test OperationObject", \testOperationObject());
        addTestCase("Test ExternalDocumentationObject", \testExternalDocumentationObject());
        addTestCase("Test ParameterObject", \testParameterObject());
        addTestCase("Test ItemsObject", \testItemsObject());
        addTestCase("Test ResponsesObject", \testResponsesObject());
        addTestCase("Test ResponseObject", \testResponseObject());
        addTestCase("Test HeaderObject", \testHeaderObject());
        addTestCase("Test TagObject", \testTagObject());
        addTestCase("Test SchemaObject", \testSchemaObject());
        addTestCase("Test XmlObject", \testXmlObject());
        addTestCase("Test SecuritySchemeObject", \testSecuritySchemeObject());
        addTestCase("Test ScopesObject", \testScopesObject());
        addTestCase("Test validation", \testValidation());
        addTestCase("Test examples", \testExamples());
        addTestCase("Test real msgs", \testRealMessages());

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    checkModules() {
%ifdef NoJson
        testSkip("no json module");
%endif
%ifdef NoYaml
        testSkip("no yaml module");
%endif
    }

    testFiles() {
        checkModules();

        {
            Swagger::SwaggerSchema so;
            string basePath = normalize_dir(dirname(get_script_path())) + DirSep;

            # Should not throw.
            so = Swagger::SwaggerLoader::fromFile(basePath + "PetStore.swagger.json");
            assertTrue(exists so);
            assertEq("/v2", so.getBasePath());
            delete so;
            so = Swagger::SwaggerLoader::fromFile(basePath + "PetStore.swagger.yaml");
            assertTrue(exists so);
            assertEq("/v2", so.getBasePath());

            assertThrows("EMPTY-SCHEMA-FILE", sub() {so = Swagger::SwaggerLoader::fromFile(basePath + "EmptySwagger.yaml");});
            assertThrows("EMPTY-SCHEMA-FILE", sub() {so = Swagger::SwaggerLoader::fromFile(basePath + "EmptySwagger.json");});
        }

        {
            hash soh = ValidSwaggerSchema + (
                "paths": (
                    "/test": (
                        "get": (
                            "operationId": "test",
                            "responses": (
                                "200": (
                                    "description": "test",
                                ),
                            ),
                            "parameters": ((
                                "in": "body",
                                "name": "ignored",
                                "required": True,
                                "schema": (
                                    "$ref": "other.json#/info",
                                ),
                            ),),
                        ),
                    ),
                ),
            );
            assertThrows("FILE-OPEN2-ERROR", "other.json", sub () {SwaggerSchema so("", soh);});

            code try_import = string sub (string fn) { return OtherJsonErr; };
            assertThrows("INVALID-REFERENCE", "other.json.*info", sub () {SwaggerSchema so("", soh, ("try_import": try_import));});
            try_import = string sub (string fn) { return OtherJsonOk; };
            {
                SwaggerSchema so("", soh, ("try_import": try_import));
                assertEq("/", so.getBasePath());
            }

            soh.paths."/test".get.parameters[0].schema."$ref" = "other.yaml#/info";
            assertThrows("FILE-OPEN2-ERROR", "other.yaml", sub () {SwaggerSchema so("", soh);});

            try_import = string sub (string fn) { return OtherYamlErr; };
            assertThrows("INVALID-REFERENCE", "other.yaml.*info", sub () {SwaggerSchema so("", soh, ("try_import": try_import));});
            try_import = string sub (string fn) { return OtherYamlOk; };
            SwaggerSchema so("", soh, ("try_import": try_import));
        }
    }

    testSwaggerSchema() {
        checkModules();

        assertThrows("INVALID-FIELD-TYPE", loadjson, '{"swagger": true}');
        assertThrows("REQUIRED-FIELD-MISSING", loadjson, '{}', "missing swagger field");
        assertThrows("REQUIRED-FIELD-MISSING", loadjson, '{"swagger": 2.0}', "missing info field");
        assertThrows("INVALID-FIELD-TYPE", loadjson, '{"swagger": 2.0, "info": []}', "invalid info field type");
        assertThrows("REQUIRED-FIELD-MISSING", loadjson, '{"swagger": 2.0, "info": {"title":"", "version":""}}', "missing paths field");

        assertTrue(loadjson(ValidSwaggerSchemaJSON));
        assertTrue(loadyaml(ValidSwaggerSchemaYAML));

        # Used for following tests.
        hash base = ValidSwaggerSchema;

        assertTrue(loadyaml(my_make_yaml(base + {"host": "localhost"})));
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"host": 2.0}), "invalid host field type");

        assertTrue(loadyaml(my_make_yaml(base + {"basePath": "/test"})));
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"basePath": "gfhgf"}), "invalid basePath format");
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"basePath": 2.0}), "invalid basePath field type");

        assertTrue(loadyaml(my_make_yaml(base + {"schemes": ("http",)})));
        assertTrue(loadyaml(my_make_yaml(base + {"schemes": ("https",)})));
        assertTrue(loadyaml(my_make_yaml(base + {"schemes": ("ws",)})));
        assertTrue(loadyaml(my_make_yaml(base + {"schemes": ("wss",)})));
        assertThrows("INVALID-FIELD-TYPE", "schemes", loadyaml, my_make_yaml(base + {"schemes": 5}));
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"schemes": ("a",)}));

        assertTrue(loadyaml(my_make_yaml(base + {"consumes": ("test", "lalala")})));
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"consumes": "2.0"}), "invalid consumes field type");
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"consumes": (2, 5)}), "invalid consumes value type");

        assertTrue(loadyaml(my_make_yaml(base + {"produces": ("test", "lalala")})));
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"produces": "2.0"}), "invalid produces field type");
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"produces": (2, 5)}), "invalid produces value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"definitions": 2}), "invalid definitions field type");
        assertThrows("INVALID-FIELD-TYPE", "invalid definition datatype", loadyaml,
            my_make_yaml(base + {"definitions": {"a": 5}}), "invalid definitions field value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"parameters": 2}), "invalid parameters field type");
        assertThrows("INVALID-FIELD-TYPE", "invalid parameter datatype", loadyaml,
            my_make_yaml(base + {"parameters": {"a": 5}}), "invalid parameters field value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"responses": 2}), "invalid responses field type");
        assertThrows("INVALID-FIELD-TYPE", "invalid response datatype", loadyaml,
            my_make_yaml(base + {"responses": {"a": 5}}), "invalid responses field value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"securityDefinitions": 2}), "invalid securityDefinitions field type");
        assertThrows("INVALID-FIELD-TYPE", "invalid security definition datatype", loadyaml,
            my_make_yaml(base + {"securityDefinitions": {"a": 5}}), "invalid securityDefinitions field value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"security": 2}), "invalid security field type");
        assertTrue(loadyaml(my_make_yaml(base + {"security": list()})));
        assertThrows("INVALID-FIELD-TYPE", "\"security\".*type \"integer\"", loadyaml,
            my_make_yaml(base + {"security": (2, 5)}), "invalid security field value type");
        assertThrows("INVALID-FIELD-TYPE", "\"security\".*\"integer\"", loadyaml,
            my_make_yaml(base + {"security": ({"a": "b"}, {"b": 2})}), "invalid security field value type");
        assertThrows("SECURITY-ERROR", "security schemes.*not defined", loadyaml,
            my_make_yaml(base + {"security": ({"a": "b"}, {"b": "a"})}), "invalid security values");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"tags": 2}), "invalid tags field type");
        assertTrue(loadyaml(my_make_yaml(base + {"tags": list()})));
        assertThrows("INVALID-FIELD-TYPE", "invalid tag datatype", loadyaml,
            my_make_yaml(base + {"tags": (5, {})}), "invalid tags field value type");

        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"externalDocs": 2}), "invalid externalDocs field type");

        {
            hash soh = ValidSwaggerSchema + (
                "paths": (
                    "/test": (
                        "get": (
                            "operationId": "test",
                            "responses": (
                                "200": (
                                    "description": "test",
                                ),
                            ),
                            "parameters": ((
                                "in": "header",
                                "name": "Test-Header",
                                "type": "string",
                                "required": True,
                                "default": "Test",
                            ),),
                        ),
                    ),
                ),
            );
            SwaggerSchema so("", soh);

            {
                string uri_path = "/test";
                hash hdr = ("Test-Header": "Test");
                assertEq(Type::Hash, so.processRequest("get", uri_path, NOTHING, hdr).type());
                assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("get", uri_path));
            }

            {
                string uri_path = "/test";
                hash hdr = ("Test-Header": 1);
                assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("get", uri_path, NOTHING, hdr));
            }
        }

        {
            hash soh = ValidSwaggerSchema + (
                "paths": (
                    "/test": (
                        "get": (
                            "operationId": "test",
                            "responses": (
                                "200": (
                                    "description": "test",
                                    "schema": {
                                        "type": "string"
                                    }
                                ),
                                "400": (
                                    "description": "test",
                                    "schema": {
                                        "type": "string"
                                    }
                                ),
                            ),
                            "parameters": ((
                                "in": "query",
                                "name": "bin",
                                "type": "string",
                                "format": "byte",
                            ),),
                            "consumes": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                            "produces": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                        ),
                    ),
                ),
            );

            {
                SwaggerSchema so("", soh);

                string uri_path = "/test?bin=YWJj";
                hash<RestRequestClientInfo> h = so.processRequest("get", uri_path);
                assertEq(Type::Hash, h.type());
                assertEq("/test?bin=YWJj", h.uri_path);

                hash<HttpResponseInfo> res = so.processResponse("get", "/test", 200, "OK", NOTHING, MimeTypeJson);
                assertEq(200, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");
                assertEq("\"OK\"", res.body);

                hash<RestResponseClientInfo> cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                assertEq(200, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq("OK", cres.body);
                #printf("cres: %N\n", cres);

                res = so.processResponse("get", "/test", 400, "ERR", NOTHING, MimeTypeJson);
                assertEq(400, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");
                assertEq("\"ERR\"", res.body);
                #printf("res: %N\n", res);

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                assertEq(400, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq("ERR", cres.body);
            }

            soh.definitions = (
                "robj": (
                    "type": "object",
                    "properties": (
                        "a": (
                            "type": "string",
                        ),
                        "b": (
                            "type": "integer",
                        ),
                    ),
                ),
                "nobj": {
                    "type": "object",
                    "additionalProperties": True,
                },
                "pobj": {
                },
                "nullableobj": {
                    "type": "object",
                    "properties": {
                        "a": {
                            "type": "string",
                            "x-nullable": True,
                        },
                    },
                },
            );
            soh.paths."/test".get.responses."200".schema = {
                "$ref": "#/definitions/robj",
            };
            soh.paths."/test".get.responses."404" = {
                "description": "test",
                "schema": {
                    "$ref": "#/definitions/nobj",
                },
            };
            soh.paths."/test".get.responses."500" = {
                "description": "test",
                "schema": {
                    "$ref": "#/definitions/pobj",
                },
            };
            soh.paths."/test".get.responses."501" = {
                "description": "test",
                "schema": {
                    "$ref": "#/definitions/nullableobj",
                },
            };

            {
                SwaggerSchema so("", soh);

                assertThrows("SCHEMA-VALIDATION-ERROR", \so.processResponse(), ("get", "/test", 200, "OK", NOTHING, MimeTypeJson));

                hash body = (
                    "a": "hi",
                    "b": 2,
                );
                hash<HttpResponseInfo> res = so.processResponse("get", "/test", 200, body, NOTHING, MimeTypeJson);
                assertEq(200, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");
                assertEq(Type::String, res.body.type());
%ifndef NoJson
                assertEq(body, parse_json(res.body));
%endif

                hash<RestResponseClientInfo> cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                assertEq(200, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq(body, cres.body);
                #printf("cres: %N\n", cres);

                res = so.processResponse("get", "/test", 400, "ERR", NOTHING, MimeTypeJson);
                assertEq(400, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");
                assertEq("\"ERR\"", res.body);

                assertThrows("SCHEMA-VALIDATION-ERROR", \so.processResponse(), ("get", "/test", 200, ("x": 2), NOTHING, MimeTypeJson));
                #printf("res: %N\n", res);

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                assertEq(400, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq("ERR", cres.body);
                #printf("cres: %N\n", cres);

                res = so.processResponse("get", "/test", 404, {"a": 1}, NOTHING, MimeTypeJson);
                #printf("res: %N\n", res);
                assertEq(404, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                #printf("cres: %N\n", cres);
                assertEq(404, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq({"a": 1}, cres.body);

                res = so.processResponse("get", "/test", 500, {"a": 1}, NOTHING, MimeTypeJson);
                #printf("res: %N\n", res);
                assertEq(500, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                #printf("cres: %N\n", cres);
                assertEq(500, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq({"a": 1}, cres.body);

                res = so.processResponse("get", "/test", 500, 123, NOTHING, MimeTypeJson);
                #printf("res: %N\n", res);
                assertEq(500, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                #printf("cres: %N\n", cres);
                assertEq(500, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq(123, cres.body);

                res = so.processResponse("get", "/test", 501, {"a": "abc"}, NOTHING, MimeTypeJson);
                #printf("res: %N\n", res);
                assertEq(501, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                #printf("cres: %N\n", cres);
                assertEq(501, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq({"a": "abc"}, cres.body);

                res = so.processResponse("get", "/test", 501, {"a": NOTHING}, NOTHING, MimeTypeJson);
                #printf("res: %N\n", res);
                assertEq(501, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");

                cres = so.parseResponse("get", "/test", res.code, res.body, res.hdr);
                #printf("cres: %N\n", cres);
                assertEq(501, cres.code);
                assertEq(MimeTypeJson, cres.hdr."Content-Type");
                assertEq({"a": NOTHING}, cres.body);
            }
        }

        { # bug #2894 test that if schema is missing from response, then no body should be present for the response
          # also test that if schema is present and there is no response body, exception should be thrown
            hash soh = ValidSwaggerSchema + (
                "paths": (
                    "/test": (
                        "get": (
                            "operationId": "test",
                            "responses": (
                                "200": (
                                    "description": "test",
                                    "schema": {
                                        "type": "string"
                                    }
                                ),
                                "400": (
                                    "description": "test",
                                ),
                            ),
                            "consumes": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                            "produces": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                        ),
                    ),
                ),
            );

            {
                SwaggerSchema so("", soh);
                hash<HttpResponseInfo> res;
                hash<RestResponseClientInfo> cres;
                hash hdr = {"Content-Type" : "application/json"};

                code c = sub () { res = so.processResponse("get", "/test", 200, NOTHING, NOTHING, MimeTypeJson); };
                assertThrows("SCHEMA-VALIDATION-ERROR", "Param.*response body.*is null, but.*expected", c);

                c = sub () { cres = so.parseResponse("get", "/test", 200, NOTHING, hdr); };
                assertThrows("SCHEMA-VALIDATION-ERROR", "Param.*response body.*is null, but.*expected", c);

                c = sub () { res = so.processResponse("get", "/test", 400, "ERR", NOTHING, MimeTypeJson); };
                assertThrows("SCHEMA-VALIDATION-ERROR", "should not have any body", c);

                c = sub () { cres = so.parseResponse("get", "/test", 400, "\"ERR\"", hdr); };
                assertThrows("SCHEMA-VALIDATION-ERROR", "should not have any body", c);
            }
        }

        {
            hash<auto> soh = ValidSwaggerSchema + (
                "paths": (
                    "/test": (
                        "get": (
                            "operationId": "test",
                            "parameters": (
                                {
                                    "name": "float",
                                    "in": "query",
                                    "description": "desc",
                                    "required": False,
                                    "type": "number",
                                    "format": "float",
                                },
                            ),
                            "responses": (
                                "200": (
                                    "description": "test",
                                    "schema": {
                                        "type": "string"
                                    }
                                ),
                                "400": (
                                    "description": "test",
                                ),
                            ),
                            "consumes": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                            "produces": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                        ),
                    ),
                ),
            );

            {
                SwaggerSchema so("", soh);
                string uri_path = "/test?float=1.1";
                hash<auto> hdr = {"Test-Header": "Test"};
                hash<RestRequestServerInfo> req = so.parseRequest("GET", uri_path, NOTHING, \hdr);
                assertEq(Type::Float, req.query.float.type());
            }

            {
                soh.paths."/test".get.parameters[0].format = "xxx";
                assertThrows("INVALID-FIELD-VALUE", sub () { SwaggerSchema so("", soh); });
            }
        }

        {
            hash<auto> soh = ValidSwaggerSchema + (
                "paths": (
                    "/cust/{id}": (
                        "get": (
                            "operationId": "get-customer",
                            "parameters": (
                                {
                                    "name": "id",
                                    "in": "path",
                                    "description": "customer ID",
                                    "required": True,
                                    "type": "integer",
                                },
                            ),
                            "responses": (
                                "200": (
                                    "description": "test",
                                    "schema": {
                                        "type": "string"
                                    }
                                ),
                                "400": (
                                    "description": "test",
                                ),
                            ),
                            "consumes": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                            "produces": (
                                MimeTypeJson,
                                MimeTypeYaml,
                            ),
                        ),
                    ),
                ),
            );

            {
                SwaggerSchema so("", soh);
                string uri_path = "/cust/1";
                hash<auto> hdr;
                hash<RestRequestServerInfo> req = so.parseRequest("GET", uri_path, NOTHING, \hdr);
                on_error printf("req: %y\n", req);
                assertEq(1, req.path_args.id);

                uri_path = "cust/1";
                req = so.parseRequest("GET", uri_path, NOTHING, \hdr);
                on_error printf("req: %y\n", req);
                assertEq(1, req.path_args.id);
            }
        }
    }

    testInfoObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash info;

        assertThrows("REQUIRED-FIELD-MISSING", loadyaml, my_make_yaml(base + {"info": {}}));
        assertThrows("REQUIRED-FIELD-MISSING", "\"version\".*missing", loadyaml, my_make_yaml(base + {"info": {"title": "a"}}));
        assertThrows("REQUIRED-FIELD-MISSING", "\"title\".*missing", loadyaml, my_make_yaml(base + {"info": {"version": "a"}}));
        assertThrows("INVALID-FIELD-TYPE", "title", loadyaml, my_make_yaml(base + {"info": {"title": 5, "version": "a"}}));
        assertThrows("INVALID-FIELD-TYPE", "version", loadyaml, my_make_yaml(base + {"info": {"title": "a", "version": 1.0}}));
        loadyaml(my_make_yaml(base + {"info": {"title": "a", "version": "1.0.0"}}));
        loadyaml(my_make_yaml(base + {"info": {"title": "a", "version": "1.0"}}));

        info = ValidInfoObject + {"description": 5};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"description": "lorem ipsum"};
        assertTrue(loadyaml(my_make_yaml(base + {"info": info})));

        info = ValidInfoObject + {"termsOfService": 5};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"termsOfService": "lorem ipsum"};
        assertTrue(loadyaml(my_make_yaml(base + {"info": info})));

        info = ValidInfoObject + {"contact": 5};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"license": 5};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"info": info}));
    }

    testContactObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash info;

        info = ValidInfoObject + {"contact": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"info": info})));
        info = ValidInfoObject + {"contact": {"name": 5}};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"contact": {"url": 5}};
        assertThrows("INVALID-FIELD-TYPE", "url", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"contact": {"email": 5}};
        assertThrows("INVALID-FIELD-TYPE", "email", loadyaml, my_make_yaml(base + {"info": info}));
    }

    testLicenseObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash info;

        info = ValidInfoObject + {"license": {"name": "name"}};
        assertTrue(loadyaml(my_make_yaml(base + {"info": info})));
        info = ValidInfoObject + {"license": {"url": "a"}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"name\".*missing", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"license": {"name": 5}};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"info": info}));
        info = ValidInfoObject + {"license": {"name": "name", "url": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"info": info})));
        info = ValidInfoObject + {"license": {"name": "a", "url": 5}};
        assertThrows("INVALID-FIELD-TYPE", "url", loadyaml, my_make_yaml(base + {"info": info}));
    }

    testPathsObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash p;

        p = {};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": p})));
        p = base.parameters + {"a": {}};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"paths": p}));
    }

    testPathItemObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash p;

        p = {"/a": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": p})));

        # reference checks
        p = {"/a": {"$ref": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": p})));
        p = {"/a": {"$ref": 5}};
        assertThrows("INVALID-FIELD-TYPE", "ref", loadyaml, my_make_yaml(base + {"paths": p}));

        # operations checks
        p = {"/a": {"get": 5}};
        assertThrows("INVALID-FIELD-TYPE", "get", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"put": 5}};
        assertThrows("INVALID-FIELD-TYPE", "put", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"post": 5}};
        assertThrows("INVALID-FIELD-TYPE", "post", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"delete": 5}};
        assertThrows("INVALID-FIELD-TYPE", "delete", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"options": 5}};
        assertThrows("INVALID-FIELD-TYPE", "options", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"head": 5}};
        assertThrows("INVALID-FIELD-TYPE", "head", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"patch": 5}};
        assertThrows("INVALID-FIELD-TYPE", "patch", loadyaml, my_make_yaml(base + {"paths": p}));
        p = {"/a": {"get": 5}};
        assertThrows("INVALID-FIELD-TYPE", "get", loadyaml, my_make_yaml(base + {"paths": p}));

        # parameters checks
        p = {"/a": {"parameters": list()}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": p})));
        p = {"/a": {"parameters": ({"$ref": "#/parameters/a"}, {"name": "a", "in": "query", "type": "string"})}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": p})));
        p = {"/a": {"parameters": 5}};
        assertThrows("INVALID-FIELD-TYPE", "parameters", loadyaml, my_make_yaml(base + {"paths": p}));
    }

    testOperationObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash r = {"200": {"description": "a"}};
        hash p;

        p = {"get": {"responses": r}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));

        # tags checks
        p = {"get": {"responses": r, "tags": ("a", {"name": "a"})}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "tags": 5}};
        assertThrows("INVALID-FIELD-TYPE", "tags", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # summary checks
        p = {"get": {"responses": r, "summary": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "summary": 5}};
        assertThrows("INVALID-FIELD-TYPE", "summary", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # description checks
        p = {"get": {"responses": r, "description": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "description": 5}};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # deprecated checks
        p = {"get": {"responses": r, "deprecated": True}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "deprecated": 5}};
        assertThrows("INVALID-FIELD-TYPE", "deprecated", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # externalDocs checks
        p = {"get": {"responses": r, "externalDocs": {"url": "a"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "externalDocs": 5}};
        assertThrows("INVALID-FIELD-TYPE", "externalDocs", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # operationId checks
        p = {"get": {"responses": r, "operationId": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "operationId": 5}};
        assertThrows("INVALID-FIELD-TYPE", "operationId", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # consumes checks
        p = {"get": {"responses": r, "consumes": ("a",)}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "consumes": (5, 3)}};
        assertThrows("INVALID-FIELD-TYPE", "consumes.*list value", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "consumes": 5}};
        assertThrows("INVALID-FIELD-TYPE", "consumes", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # produces checks
        p = {"get": {"responses": r, "produces": ("a",)}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "produces": (5, 3)}};
        assertThrows("INVALID-FIELD-TYPE", "produces.*list value", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "produces": 5}};
        assertThrows("INVALID-FIELD-TYPE", "produces", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # parameters checks
        p = {"get": {"responses": r, "parameters": ({"$ref": "#/parameters/a"},)}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "parameters": 5}};
        assertThrows("INVALID-FIELD-TYPE", "parameters", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # responses checks
        p = {"get": {"responses": 5}};
        assertThrows("INVALID-FIELD-TYPE", "responses", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # schemes checks
        p = {"get": {"responses": r, "schemes": ("http", "https", "ws", "wss")}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "schemes": ("a",)}};
        assertThrows("INVALID-FIELD-VALUE", "invalid.*transfer protocol", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "schemes": (5, 2)}};
        assertThrows("INVALID-FIELD-TYPE", "schemes.*list value", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "schemes": 5}};
        assertThrows("INVALID-FIELD-TYPE", "schemes", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # security checks
        p = {"get": {"responses": r, "security": ({"a":("b",)},)}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": r, "security": 5}};
        assertThrows("INVALID-FIELD-TYPE", "security", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "security": (5,)}};
        assertThrows("INVALID-FIELD-TYPE", "security.*list value", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "security": ({"a":5},)}};
        assertThrows("INVALID-FIELD-TYPE", "value type", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": r, "security": ({"a":(5,)},)}};
        assertThrows("INVALID-FIELD-TYPE", "value type", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));

        # check validation method
        hash<string, bool> mime_types();
        hash<UriQueryInfo> h({"method": "/create"});
        string filePath = get_script_dir() + DirSep + "Multipart.swagger.yaml";
        SwaggerSchema swagger = Swagger::SwaggerLoader::fromFile(filePath);
        hash oh = SwaggerLoader::parseSchemaContent(filePath, ReadOnlyFile::readTextFile(filePath));
        OperationObject op = new OperationObject("/create", "post", oh.paths."/create".post, swagger);
        string body = "body";
        hash<auto> headers = {"header": "header"};

        assertThrows("SCHEMA-VALIDATION-ERROR",
            'Parameter \"formData.upfile\".*expected type is \"file\"',
            sub() {op.validateRequest(False, new PathItemObject("/create", {}, swagger), \h, \body, \headers,
                \mime_types); });
    }

    testExternalDocumentationObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash extDocs;

        extDocs = {"externalDocs": {"url": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + extDocs)));
        extDocs = {"externalDocs": {"description": "a"}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"url\".*missing", loadyaml, my_make_yaml(base + extDocs));
        extDocs = {"externalDocs": {"url": 5}};
        assertThrows("INVALID-FIELD-TYPE", "url", loadyaml, my_make_yaml(base + extDocs));
        extDocs = {"externalDocs": {"url": "a", "description": "b"}};
        assertTrue(loadyaml(my_make_yaml(base + extDocs)));
        extDocs = {"externalDocs": {"url": "a", "description": 5}};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + extDocs));
    }

    testParameterObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash p;

        # name and in checks
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"in": "query", "type": "string"}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"name\".*missing", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "type": "string"}};
        assertThrows("REQUIRED-FIELD-MISSING", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": 5, "in": "query", "type": "string"}};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": 5, "type": "string"}};
        assertThrows("INVALID-FIELD-VALUE", "in", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "a", "type": "string"}};
        assertThrows("INVALID-FIELD-VALUE", "in", loadyaml, my_make_yaml(base + {"parameters": p}));

        # description checks
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "description": 5}};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"parameters": p}));

        # TODO - body location parameter - have to do SchemaObject first

        # in (location) checks
        p = base.parameters + {"a": {"name": "a", "in": "formData", "type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "header", "type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        # "required" checks
        p = base.parameters + {"a": {"name": "a", "in": "path", "type": "string", "required": False}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "path", "type": "string"}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"required\".*missing", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "path", "type": "string", "required": "a"}};
        assertThrows("INVALID-FIELD-TYPE", "required", loadyaml, my_make_yaml(base + {"parameters": p}));

        # type checks
        p = base.parameters + {"a": {"name": "a", "in": "query"}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"type\".*missing", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": 5}};
        assertThrows("INVALID-FIELD-TYPE", "\"type\"", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "a"}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "boolean"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "file"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        # format checks
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": 5}};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer", "format": "int32"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer", "format": "int64"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer", "format": "a"}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number", "format": "double"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number", "format": "float"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number", "format": "a"}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "binary"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "byte"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "date"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "date-time"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "password"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "format": "a"}};
        # issue 4203: ignore unknown string format
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        # allowEmptyValue checks
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "allowEmptyValue": 5}};
        assertThrows("INVALID-FIELD-TYPE", "allowEmptyValue", loadyaml, my_make_yaml(base + {"parameters": p}));

        # collectionFormat checks
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": 5, "items": {"type": "string"}}};
        assertThrows("INVALID-FIELD-TYPE", "collectionFormat", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "collectionFormat": "a", "items": {"type": "string"}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": {"name": "a", "in": "header", "type": "array", "collectionFormat": "multi", "items": {"type": "string"}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        # default checks

        # TODO
        /*p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}, "default": 5}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));*/

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "boolean", "default": "true"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "boolean", "default": 5}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        # TODO
        /*p = base.parameters + {"a": {"name": "a", "in": "query", "type": "file"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "file"}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));*/

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer", "default": 5}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "integer", "default": "a"}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number", "default": 2.0}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "number", "default": "a"}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "default": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "string", "default": 5}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));
    }

    testItemsObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash paramBase = {"name": "a", "in": "query", "type": "array"};
        hash p;

        # type checks
        p = base.parameters + {"a": paramBase + {"items": {}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": 5}}};
        assertThrows("INVALID-FIELD-TYPE", "\"type\"", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": paramBase + {"items": {"type": "a"}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        # TODO array of array
        /*p = base.parameters + {"a": {"name":"a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));*/
        p = base.parameters + {"a": paramBase + {"items": {"type": "boolean"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "integer"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "number"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        # format checks
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": 5}}};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "integer", "format": "int32"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "integer", "format": "int64"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "integer", "format": "a"}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "number", "format": "double"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "number", "format": "float"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "number", "format": "a"}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "binary"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "byte"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "date"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "date-time"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "password"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "format": "a"}}};
        # issue 4203: ignore unknown string format
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        # collectionFormat checks
        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": "csv", "items": {"type": "string"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": "ssv", "items": {"type": "string"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": "tsv", "items": {"type": "string"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": "pipes", "items": {"type": "string"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));

        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": 5, "items": {"type": "string"}}}};
        assertThrows("INVALID-FIELD-TYPE", "collectionFormat", loadyaml, my_make_yaml(base + {"parameters": p}));
        p = base.parameters + {"a": paramBase + {"items": {"type": "array", "collectionFormat": "a", "items": {"type": "string"}}}};
        assertThrows("INVALID-FIELD-VALUE", "collectionFormat", loadyaml, my_make_yaml(base + {"parameters": p}));

        # default checks

        # TODO array of array
        /*p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}, "default": 5}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));*/

        p = base.parameters + {"a": paramBase + {"items": {"type": "boolean", "default": True}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "boolean", "default": 5}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "integer", "default": 5}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "integer", "default": "a"}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "number", "default": 2.0}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "number", "default": "a"}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));

        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "default": "a"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"parameters": p})));
        p = base.parameters + {"a": paramBase + {"items": {"type": "string", "default": 5}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"parameters": p}));
    }

    testResponsesObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash p;

        p = {"get": {"responses": {"200": {"description": "a"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));
        p = {"get": {"responses": {"default": {"description": "a"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"paths": {"/a": p}})));

        p = {"get": {"responses": {"200": 5}}};
        assertThrows("INVALID-FIELD-TYPE", "response.*invalid type", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
        p = {"get": {"responses": {"default": 5}}};
        assertThrows("INVALID-FIELD-TYPE", "default.*invalid type", loadyaml, my_make_yaml(base + {"paths": {"/a": p}}));
    }

    testResponseObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash r;

        r = {"a": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"description\".*missing", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": 5}};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "schema": 5}};
        assertThrows("INVALID-FIELD-TYPE", "schema", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a", "headers": 5}};
        assertThrows("INVALID-FIELD-TYPE", "headers", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a", "examples": 5}};
        assertThrows("INVALID-FIELD-TYPE", "examples", loadyaml, my_make_yaml(base + {"responses": r}));
    }

    testHeaderObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash r;

        r = {"a": {"description": "a", "headers": {}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));

        # type checks
        r = {"a": {"description": "a", "headers": {"a": {}}}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"type\".*missing", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a", "headers": {"a": {"type": "boolean"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "integer"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "number"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "items": {"type": "string"}}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": 5}}}};
        assertThrows("INVALID-FIELD-TYPE", "type", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a", "headers": {"a": {"type": "a"}}}};
        assertThrows("INVALID-FIELD-VALUE", "type", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "items": 5}}}};
        assertThrows("INVALID-FIELD-TYPE", "items", loadyaml, my_make_yaml(base + {"responses": r}));

        # TODO array type

        # format checks
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": 5}}}};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "integer", "format": "int32"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "integer", "format": "int64"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "integer", "format": "a"}}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "number", "format": "double"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "number", "format": "float"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "number", "format": "a"}}}};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "binary"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "byte"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "date"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "date-time"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "password"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "format": "a"}}}};
        # issue 4203: ignore unknown string format
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));

        # collectionFormat checks
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": "csv", "items": {"type": "string"}}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": "ssv", "items": {"type": "string"}}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": "tsv", "items": {"type": "string"}}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": "pipes", "items": {"type": "string"}}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));

        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": 5, "items": {"type": "string"}}}}};
        assertThrows("INVALID-FIELD-TYPE", "collectionFormat", loadyaml, my_make_yaml(base + {"responses": r}));
        r = {"a": {"description": "a", "headers": {"a": {"type": "array", "collectionFormat": "a", "items": {"type": "string"}}}}};
        assertThrows("INVALID-FIELD-VALUE", "collectionFormat", loadyaml, my_make_yaml(base + {"responses": r}));

        # default checks

        # TODO array of array
        /*p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        p = base.parameters + {"a": {"name": "a", "in": "query", "type": "array", "items": {"type": "string"}, "default": 5}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"responses": r}));*/

        r = {"a": {"description": "a", "headers": {"a": {"type": "boolean", "default": True}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "boolean", "default": 5}}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "integer", "default": 5}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "integer", "default": "a"}}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "number", "default": 2.0}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "number", "default": "a"}}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"responses": r}));

        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "default": "a"}}}};
        assertTrue(loadyaml(my_make_yaml(base + {"responses": r})));
        r = {"a": {"description": "a", "headers": {"a": {"type": "string", "default": 5}}}};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"responses": r}));
    }

    testTagObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash tag;

        tag = {"name": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"tags": (tag,)})));
        tag = {};
        assertThrows("REQUIRED-FIELD-MISSING", "\"name\".*missing", loadyaml, my_make_yaml(base + {"tags": (tag,)}));
        tag = {"name": 5};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"tags": (tag,)}));

        tag = {"name": "a", "description": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"tags": (tag,)})));
        tag = {"name": "a", "description": 5};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"tags": (tag,)}));

        tag = {"name": "a", "externalDocs": 5};
        assertThrows("INVALID-FIELD-TYPE", "externalDocs", loadyaml, my_make_yaml(base + {"tags": (tag,)}));
    }

    testSchemaObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash s;

        # ref checks
        s = {"$ref": "#/definitions/x"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"x": ("type": "string"), "a": s}})));
        s = {"$ref": 5};
        assertThrows("INVALID-FIELD-TYPE", "ref.*invalid type", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"$ref": "#/definitions/y"};
        assertThrows("INVALID-REFERENCE", "cannot resolve", loadyaml, my_make_yaml(base + {"definitions": {"x": ("type": "string"), "a": s}}));
        # issue #3843: sibling elements to a $ref must be ignored
        s = {"$ref": "#/definitions/x", "a": 1};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"x": ("type": "string"), "a": s}})));

        # type checks
        s = {};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": 5};
        assertThrows("INVALID-FIELD-TYPE", "\"type\"", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "a"};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # TODO array of array
        /*p = base.parameters + {"a": {"name":"a", "in": "query", "type": "array", "items": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));*/
        s = {"type": "boolean"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "integer"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "number"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));

        # format checks
        s = {"type": "string", "format": 5};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "integer", "format": "int32"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "integer", "format": "int64"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "integer", "format": "a"};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "number", "format": "double"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "number", "format": "float"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "number", "format": "a"};
        assertThrows("INVALID-FIELD-VALUE", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "format": "binary"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "format": "byte"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "format": "date"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "format": "date-time"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "format": "password"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "format": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));

        # title checks
        s = {"type": "string", "title": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "title": 5};
        assertThrows("INVALID-FIELD-TYPE", "title", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # description checks
        s = {"type": "string", "description": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "description": 5};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # items checks

        # default checks
        # TODO arrays
        /*p = {"type": "array", "items": {"type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        p = {"type": "array", "items": {"type": "string"}, "default": 5};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));*/

        s = {"type": "boolean", "default": True};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "boolean", "default": 5};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # TODO files
        /*p = {"type": "file"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        p = {"type": "file"};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));*/

        s = {"type": "integer", "default": 5};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "integer", "default": "a"};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "number", "default": 2.0};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "number", "default": "a"};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "default": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "default": 5};
        assertThrows("INVALID-FIELD-TYPE", "default", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # maxProperties checks
        s = {"type": "object", "maxProperties": 0};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "maxProperties": 5};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "maxProperties": -2};
        assertThrows("INVALID-FIELD-FORMAT", "maxProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "object", "maxProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "maxProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "maxProperties": 0};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "maxProperties": 5};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "maxProperties": -2};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "maxProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "maxProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # minProperties checks
        s = {"type": "object", "minProperties": 0};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "minProperties": 5};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "minProperties": -2};
        assertThrows("INVALID-FIELD-FORMAT", "minProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "object", "minProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "minProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "minProperties": 0};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "minProperties": 5};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "minProperties": -2};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "minProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "minProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # properties checks
        s = {"type": "object", "properties": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "properties": {"a": {"type": "string"}}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "properties": {"a": 5}};
        assertThrows("INVALID-FIELD-FORMAT", "properties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "object", "properties": 5};
        assertThrows("INVALID-FIELD-TYPE", "properties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "properties": {}};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "properties": {"a": {"type": "string"}}};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "properties": {"a": 5}};
        assertThrows("INVALID-FIELD-FORMAT", "properties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "properties": 5};
        assertThrows("INVALID-FIELD-TYPE", "properties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # additionalProperties checks
        s = {"type": "object", "additionalProperties": True};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "additionalProperties": {"type": "string"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "object", "additionalProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "additionalProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        s = {"type": "string", "additionalProperties": True};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "additionalProperties": {"type": "string"}};
        assertThrows("INVALID-FIELD-FORMAT", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "additionalProperties": "a"};
        assertThrows("INVALID-FIELD-TYPE", "additionalProperties", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # "required" checks
        s = {"type": "string", "required": ("a",)};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "required": list()};
        assertThrows("INVALID-FIELD-FORMAT", "required", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "required": (5, 3)};
        assertThrows("INVALID-FIELD-TYPE", "required", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "required": 5};
        assertThrows("INVALID-FIELD-TYPE", "required", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # discriminator checks
        s = {"type": "string", "discriminator": "a", "required": ("a", "b")};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "discriminator": "x", "required": ("a", "b")};
        assertThrows("INVALID-FIELD-VALUE", "discriminator", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "discriminator": 5};
        assertThrows("INVALID-FIELD-TYPE", "discriminator", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # readOnly checks
        s = {"type": "string", "readOnly": False};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "readOnly": True};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "readOnly": 5};
        assertThrows("INVALID-FIELD-TYPE", "readOnly", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # allOf Checks
        s = {"type": "string", "allOf": ({"$ref": "#/definitions/x"}, {"type": "string"})};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"x": ("type": "string"), "a": s}})));
        s = {"type": "string", "allOf": list()};
        assertThrows("INVALID-FIELD-FORMAT", "allOf", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "allOf": (5, 3)};
        assertThrows("INVALID-FIELD-TYPE", "allOf", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
        s = {"type": "string", "allOf": 5};
        assertThrows("INVALID-FIELD-TYPE", "allOf", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # xml checks
        s = {"type": "string", "xml": {"name": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": 5};
        assertThrows("INVALID-FIELD-TYPE", "xml", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # externalDocs checks
        s = {"type": "string", "externalDocs": {"url": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "externalDocs": 5};
        assertThrows("INVALID-FIELD-TYPE", "externalDocs", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
    }

    testXmlObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash s;

        s = {"type": "string", "xml": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));

        # name checks
        s = {"type": "string", "xml": {"name": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"name": 5}};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # namespace checks
        s = {"type": "string", "xml": {"namespace": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"namespace": 5}};
        assertThrows("INVALID-FIELD-TYPE", "namespace", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # prefix checks
        s = {"type": "string", "xml": {"prefix": "a"}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"prefix": 5}};
        assertThrows("INVALID-FIELD-TYPE", "prefix", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # attribute checks
        s = {"type": "string", "xml": {"attribute": False}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"attribute": True}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"attribute": 5}};
        assertThrows("INVALID-FIELD-TYPE", "attribute", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));

        # wrapped checks
        s = {"type": "string", "xml": {"wrapped": False}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"wrapped": True}};
        assertTrue(loadyaml(my_make_yaml(base + {"definitions": {"a": s}})));
        s = {"type": "string", "xml": {"wrapped": 5}};
        assertThrows("INVALID-FIELD-TYPE", "wrapped", loadyaml, my_make_yaml(base + {"definitions": {"a": s}}));
    }

    testSecuritySchemeObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash sd;

        # type checks
        sd = {"type": "basic"};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "a"};
        assertThrows("INVALID-SCHEME-TYPE", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": 5};
        assertThrows("INVALID-FIELD-TYPE", "\"type\"", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # description checks
        sd = {"type": "basic", "description": "a"};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "basic", "description": 5};
        assertThrows("INVALID-FIELD-TYPE", "description", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # type=apiKey checks
        sd = {"type": "apiKey"};
        assertThrows("REQUIRED-FIELD-MISSING", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "apiKey", "name": "a"};
        assertThrows("REQUIRED-FIELD-MISSING", "\"in\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "apiKey", "in": "query"};
        assertThrows("REQUIRED-FIELD-MISSING", "\"name\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "apiKey", "name": "a", "in": "query"};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "apiKey", "name": "a", "in": "header"};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "apiKey", "name": "a", "in": "a"};
        assertThrows("INVALID-APIKEY-LOCATION", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "apiKey", "name": 5, "in": "query"};
        assertThrows("INVALID-FIELD-TYPE", "name", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "apiKey", "name": "a", "in": 5};
        assertThrows("INVALID-FIELD-TYPE", "in", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # type=oauth2 checks
        sd = {"type": "oauth2"};
        assertThrows("REQUIRED-FIELD-MISSING", "\"flow\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        sd = {"type": "oauth2", "flow": 5, "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "flow", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "abc", "scopes": {}};
        assertThrows("INVALID-FLOW", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "application", "scopes": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"tokenUrl\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "password", "scopes": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"tokenUrl\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "implicit", "scopes": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"authorizationUrl\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "accessCode", "authorizationUrl": "a", "scopes": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"tokenUrl\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "accessCode", "tokenUrl": "a", "scopes": {}};
        assertThrows("REQUIRED-FIELD-MISSING", "\"authorizationUrl\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "password", "tokenUrl": "a"};
        assertThrows("REQUIRED-FIELD-MISSING", "\"scopes\".*missing", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # application and password flow
        sd = {"type": "oauth2", "flow": "application", "tokenUrl": "a", "scopes": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "oauth2", "flow": "application", "tokenUrl": 5, "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "tokenUrl", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "password", "tokenUrl": "a", "scopes": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "oauth2", "flow": "password", "tokenUrl": 5, "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "tokenUrl", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "password", "tokenUrl": "a", "scopes": 5};
        assertThrows("INVALID-FIELD-TYPE", "scopes", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # implicit flow
        sd = {"type": "oauth2", "flow": "implicit", "authorizationUrl": "a", "scopes": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "oauth2", "flow": "implicit", "authorizationUrl": 5, "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "authorizationUrl", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));

        # accessCode flow
        sd = {"type": "oauth2", "flow": "accessCode", "authorizationUrl": "a", "tokenUrl": "a", "scopes": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = {"type": "oauth2", "flow": "accessCode", "authorizationUrl": 5, "tokenUrl": "a", "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "authorizationUrl", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
        sd = {"type": "oauth2", "flow": "accessCode", "authorizationUrl": "a", "tokenUrl": 5, "scopes": {}};
        assertThrows("INVALID-FIELD-TYPE", "tokenUrl", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
    }

    testScopesObject() {
        checkModules();

        hash base = ValidSwaggerSchema;
        hash sdbase = {"type": "oauth2", "flow": "password", "tokenUrl": "a"};
        hash sd;

        sd = sdbase + {"scopes": {}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = sdbase + {"scopes": {"a": "b"}};
        assertTrue(loadyaml(my_make_yaml(base + {"securityDefinitions": {"a": sd}})));
        sd = sdbase + {"scopes": {"a": 5}};
        assertThrows("INVALID-FIELD-TYPE", loadyaml, my_make_yaml(base + {"securityDefinitions": {"a": sd}}));
    }

    testValidation() {
        checkModules();

        testValidation(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.yaml"));
        testValidation(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.json"));

        {
            hash b = ValidSwaggerSchema + {
                "definitions": {
                    "a": {
                        "type": "object",
                        "properties": {
                            "a": {
                                "type": "string",
                                "readOnly": True,
                            },
                        },
                    },
                },
            };
            b.parameters.a += (
                "in": "body",
                "name": "body",
                "required": True,
                "schema": (
                    "$ref": "#/definitions/a",
                ),
            );
            b.paths."/api/smtg" += (
                "post": (
                    "parameters": ((
                        "$ref": "#/parameters/a",
                    ),),
                    "responses": (
                        "200": (
                            "description": "success",
                        ),
                        "409": (
                            "description": "error",
                            "schema": (
                                "type": "string",
                                "format": "date",
                            ),
                        ),
                    ),
                    "consumes": (
                        MimeTypeJson,
                        MimeTypeYamlRpc,
                        MimeTypeFormUrlEncoded,
                        MimeTypeMultipartFormData,
                        "image/png",
                    ),
                    "produces": (
                        MimeTypeJson,
                        MimeTypeYamlRpc,
                    ),
                ),
                "put": (
                    "parameters": (
                        ("$ref": "#/parameters/enum",),
                        ("$ref": "#/parameters/int",),
                        ("$ref": "#/parameters/date",),
                        ("$ref": "#/parameters/byte",),
                        ("$ref": "#/parameters/string",),
                    ),
                    "responses": (
                        "200": (
                            "description": "success",
                        ),
                    ),
                    "consumes": (
                        MimeTypeJson,
                        MimeTypeYamlRpc,
                        MimeTypeFormUrlEncoded,
                        MimeTypeMultipartFormData,
                    ),
                    "produces": (
                        MimeTypeJson,
                        MimeTypeYamlRpc,
                    ),
                ),
            );
            b.paths."/api/b2497" += (
                "get": (
                    "responses": (
                        "200": (
                            "description": "success",
                            "schema": {
                                "type": "string",
                                "format": "binary",
                            }
                        ),
                    ),
                    "consumes": (MimeTypeJson,),
                    "produces": ("image/jpeg", "image/png"),
                ),
                "post": (
                    "responses": (
                        "200": ("description": "success",),
                    ),
                    "consumes": ("image/png",),
                    "produces": ("text/plain",),
                    "parameters": (
                        {
                            "in": "body",
                            "name": "body",
                            "schema": (
                                "type": "string",
                                "format": "binary",
                            ),
                        },
                    )
                ),
            );
            b.paths."/api/b2507" += (
                "post": (
                    "responses": (
                        "200": (
                            "description": "success",
                            "schema": {
                                "type": "string",
                            }
                        ),
                    ),
                    "consumes": (MimeTypeJson,),
                    "produces": (MimeTypeJson,),
                    "parameters": (
                        {
                            "in": "body",
                            "name": "body",
                            "schema": (
                                "type": "object",
                                "properties": {
                                    "a": {"type": "string"}
                                }
                            ),
                        },
                    )
                ),
            );
            b.paths."/api/b2517" += (
                "post": (
                    "responses": (
                        "200": ("description": "success",),
                    ),
                    "consumes": ("image/jpeg","image/png"),
                    "produces": ("text/plain",),
                    "parameters": (
                        {
                            "in": "body",
                            "name": "body",
                            "schema": (
                                "type": "string",
                                "format": "binary",
                            ),
                        },
                    )
                ),
            );

            remove b.parameters.a.type;

            SwaggerSchema so("", b);

            assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("post", "/api/smtg", ("a": "a")));

            b.definitions.a.properties.a.readOnly = False;
            so = new SwaggerSchema("", b);

            # issue #2369
            assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("post", "/api/smtg"));

            b.parameters.a.required = False;
            so = new SwaggerSchema("", b);
            hash<RestRequestClientInfo> h = so.processRequest("post", "/api/smtg");
            assertEq("/api/smtg", h.uri_path);
            assertNothing(h.body);

            # by default, parameters are optional
            remove b.parameters.a.required;
            so = new SwaggerSchema("", b);
            h = so.processRequest("post", "/api/smtg");
            assertEq("/api/smtg", h.uri_path);
            assertNothing(h.body);

            h = so.processRequest("post", "/api/smtg", ("a": "a"), NOTHING, MimeTypeFormUrlEncoded);
            assertEq("/api/smtg", h.uri_path);
            assertEq("a=a", h.body);
            assertEq(MimeTypeFormUrlEncoded, h.content);
            #printf("h: %y\n", h);

            {
                hash hdr = ("content-type": h.content);
                hash<RestRequestServerInfo> sh = so.parseRequest("post", h.uri_path, h.body, \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq("a", sh.body.a);
                #printf("sh: %N\n", sh);
            }

            {
                hash hdr = ("content-type": h.content);
                h.body = "b=a";
                assertThrows("SCHEMA-VALIDATION-ERROR", \so.parseRequest(), ("post", h.uri_path, h.body, \hdr));
            }

            # test for issue #2365
            assertThrows("SCHEMA-VALIDATION-ERROR", "Parameter \"a\".*invalid type", \so.processRequest(), ("post", "/api/smtg", ("a": 1)));

            assertThrows("SERIALIZATION-ERROR", "application/octet-stream", \so.processRequest(), ("post", "/api/smtg", ("a": "a"), NOTHING, MimeTypeOctetStream));

            h = so.processRequest("post", "/api/smtg", ("a": "a"), NOTHING, MimeTypeMultipartFormData);
            assertEq("/api/smtg", h.uri_path);
            assertEq(True, h.content =~ /^multipart\/form-data; boundary=/);
            assertEq(Type::Binary, h.body.type());
            #printf("h: %y\n", h + ("body": h.body.toString()));

            {
                hash hdr = ("content-type": h.content);
                hash<RestRequestServerInfo> sh = so.parseRequest("post", h.uri_path, h.body, \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq("a", sh.body.a);
            }

            # issue #2396 minLength and maxLength checks
            h = so.processRequest("put", "/api/smtg?string=abc", 1);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?string=abc", h.uri_path);
            assertEq(Type::String, h.body.type());

            assertThrows("SCHEMA-VALIDATION-ERROR", "enumerated values", \so.processRequest(),
                ("put", "/api/smtg?string=xyz", 1));
            assertThrows("SCHEMA-VALIDATION-ERROR", "string.*too short", \so.processRequest(),
                ("put", "/api/smtg?string=a", 1));
            assertThrows("SCHEMA-VALIDATION-ERROR", "string.*too long", \so.processRequest(),
                ("put", "/api/smtg?string=abcdefg", 1));

            {
                hash hdr = ("content-type": h.content);
                hash<RestRequestServerInfo> sh = so.parseRequest("put", h.uri_path, h.body, \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq(1, sh.body);

                assertThrows("SCHEMA-VALIDATION-ERROR", "string.*too short", \so.parseRequest(), ("put", "/api/smtg?string=a", h.body, \hdr));
                assertThrows("SCHEMA-VALIDATION-ERROR", "string.*too long", \so.parseRequest(), ("put", "/api/smtg?string=abcdefg", h.body, \hdr));
            }

            # issue #2364 enum check
            h = so.processRequest("put", "/api/smtg", 1);
            #printf("h: %y\n", h);
            assertEq("/api/smtg", h.uri_path);
            assertEq(Type::String, h.body.type());

            assertThrows("SCHEMA-VALIDATION-ERROR", "enum.*values", \so.processRequest(), ("put", "/api/smtg", 4));

            # issue #2388 int parameter check
            h = so.processRequest("put", "/api/smtg?int=1", 2);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?int=1", h.uri_path);
            assertEq(Type::String, h.body.type());

            assertThrows("SCHEMA-VALIDATION-ERROR", "integer", \so.processRequest(), ("put", "/api/smtg?int=sdf", 2));

            h = so.processRequest("put", "/api/smtg?int=-1", 2);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?int=-1", h.uri_path);
            assertEq(Type::String, h.body.type());

            h = so.processRequest("put", "/api/smtg?int=+1", 2);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?int=1", h.uri_path);
            assertEq(Type::String, h.body.type());

            {
                hash hdr."content-type" = "application/json";
                hash<RestRequestServerInfo> sh = so.parseRequest("put", h.uri_path, h.body, \hdr);
                #printf("sh: %y\n", sh);
                assertEq("/api/smtg", sh.path);
                assertEq(1, sh.query.int);
                assertEq(2, sh.body);
            }

            # issue #2388 date parameter check
            h = so.processRequest("put", "/api/smtg?date=2017-01-01", 2);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?date=2017-01-01", h.uri_path);
            assertEq(Type::String, h.body.type());

            assertThrows("SCHEMA-VALIDATION-ERROR", "date", \so.processRequest(), ("put", "/api/smtg?date=sdf", 2));

            {
                hash hdr."content-type" = "application/json";
                hash<RestRequestServerInfo> sh = so.parseRequest("put", h.uri_path, h.body, \hdr);
                #printf("sh: %y\n", sh);
                assertEq("/api/smtg", sh.path);
                assertEq(2017-01-01, sh.query.date);
                assertEq(2, sh.body);
            }

            {
                # issue #2397 400 Bad Request response to invalid date params
                hash hdr."content-type" = "application/json";
                assertThrows("SCHEMA-VALIDATION-ERROR", "date.*invalid", \so.parseRequest(), ("put", "/api/smtg?date=2017-01-x1", h.body, \hdr));
            }

            # issue #2388 byte parameter check
            h = so.processRequest("put", "/api/smtg?byte=YWJj", 3);
            #printf("h: %y\n", h);
            assertEq("/api/smtg?byte=YWJj", h.uri_path);
            assertEq(Type::String, h.body.type());

            assertThrows("SCHEMA-VALIDATION-ERROR", "byte.*base64", \so.processRequest(), ("put", "/api/smtg?byte=123", 3));

            {
                hash hdr."content-type" = "application/json";
                hash<RestRequestServerInfo> sh = so.parseRequest("put", h.uri_path, h.body, \hdr);
                #printf("sh: %y\n", sh);
                assertEq("/api/smtg", sh.path);
                assertEq(<616263>, sh.query.byte);
                assertEq(3, sh.body);
            }

            # issue #2397 400 Bad Request response to invalid date params
            b.definitions.a = (
                "type": "string",
                "format": "date",
            );

            so = new SwaggerSchema("", b);

            {
                hash hdr = ("content-type": h.content);
                hash<RestRequestServerInfo> sh = so.parseRequest("post", "/api/smtg", "\"2017-01-01\"", \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq(2017-01-01, sh.body);

                assertThrows("SCHEMA-VALIDATION-ERROR", \so.parseRequest(), ("post", "/api/smtg", "\"2017-01-0x\"", \hdr));
                #printf("sh: %N\n", sh);
            }

            # issue #2397 400 Bad Request response to invalid binary params
            b.definitions.a.format = "binary";

            so = new SwaggerSchema("", b);

            {
                hash hdr = ("content-type": "image/png");
                hash<RestRequestServerInfo> sh = so.parseRequest("post", "/api/smtg", <abcd>, \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq(<abcd>, sh.body);
                #printf("sh: %N\n", sh);
            }

            # issue #2397 400 Bad Request response to invalid byte params
            b.definitions.a.format = "byte";

            so = new SwaggerSchema("", b);

            {
                hash hdr = ("content-type": h.content);
                hash<RestRequestServerInfo> sh = so.parseRequest("post", "/api/smtg", "\"YWJjZA==\"", \hdr);
                assertEq("/api/smtg", sh.path);
                assertEq("abcd", sh.body.toString());

                assertThrows("SCHEMA-VALIDATION-ERROR", \so.parseRequest(), ("post", "/api/smtg", "\"YWJjZZZ\"", \hdr));
                #printf("sh: %N\n", sh);
            }

            # issue #2409: date values formatted incorrectly in Swagger responses
            {
                date now = now_us();

                hash<HttpResponseInfo> res = so.processResponse("post", "/api/smtg", 409, now, NOTHING, MimeTypeJson);
                assertEq(409, res.code);
                assertEq(MimeTypeJson, res.hdr."Content-Type");
                assertEq(now.format("\"YYYY-MM-DD\""), res.body);
            }

            # issue #2497 test response
            so = new SwaggerSchema("", b);
            {
                hash<HttpResponseInfo> res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/jpeg"}, NOTHING);
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/png"}, NOTHING);
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/jpeg"}, NOTHING);
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/png"}, NOTHING);
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);
            }
            # issue #2497 test response parsing with Content-Types that cannot be deserialized
            {
                hash<RestResponseClientInfo> cres = so.parseResponse("get", "/api/b2497", 200, <feedface>, ("content-type": "image/jpeg"));
                assertEq(200, cres.code);
                assertEq(MimeTypeJpeg, cres.hdr."content-type");
                assertEq(<feedface>, cres.body);
            }
            {
                hash<HttpResponseInfo> res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/jpeg"}, ("*/*",));
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/png"}, ("*/*",));
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/jpeg"}, ("*/*",));
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/png"}, ("*/*",));
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);
            }
            {
                hash<HttpResponseInfo> res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/jpeg"}, ("image/*",));
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/png"}, ("image/*",));
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/jpeg"}, ("image/*",));
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/png"}, ("image/*",));
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);
            }
            {
                list contentTypes = ("text/x-yaml","application/x-yaml","text/xml","application/xml","application/json","image/jpeg","image/png");
                hash<HttpResponseInfo> res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/jpeg"}, contentTypes);
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"content-type": "image/png"}, contentTypes);
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."content-type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/jpeg"}, contentTypes);
                assertEq(200, res.code);
                assertEq("image/jpeg", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);

                res = so.processResponse("get", "/api/b2497", 200, <feedface>, {"Content-Type": "image/png"}, contentTypes);
                assertEq(200, res.code);
                assertEq("image/png", res.hdr."Content-Type");
                assertEq(<feedface>, res.body);
            }
            {
                list contentTypes = ("text/x-yaml","application/x-yaml","text/xml","application/xml","application/json","image/jpeg","image/png");
                assertThrows("ACCEPT-ERROR", \so.processResponse(), ("get", "/api/b2497", 200, <feedface>, NOTHING, NOTHING));
                assertThrows("ACCEPT-ERROR", \so.processResponse(), ("get", "/api/b2497", 200, <feedface>, NOTHING, ("image/*",)));
                assertThrows("ACCEPT-ERROR", \so.processResponse(), ("get", "/api/b2497", 200, <feedface>, NOTHING, contentTypes));
            }

            # issue #2497 test request
            {
                hash<RestRequestClientInfo> res = so.processRequest("post", "/api/b2497", <feedface>, {"content-type": "image/png"}, NOTHING);
                assertEq("/api/b2497", res.uri_path);
                assertEq("image/png", res.content);
                assertEq(<feedface>, res.body);

                res = so.processRequest("post", "/api/b2497", <feedface>, {"Content-Type": "image/png"}, NOTHING);
                assertEq("/api/b2497", res.uri_path);
                assertEq("image/png", res.content);
                assertEq(<feedface>, res.body);
            }
            {
                hash<RestRequestClientInfo> res = so.processRequest("post", "/api/b2497", <feedface>, {"content-type": "image/png"}, ("image/png",));
                assertEq("/api/b2497", res.uri_path);
                assertEq("image/png", res.content);
                assertEq(<feedface>, res.body);

                res = so.processRequest("post", "/api/b2497", <feedface>, {"Content-Type": "image/png"}, ("image/png",));
                assertEq("/api/b2497", res.uri_path);
                assertEq("image/png", res.content);
                assertEq(<feedface>, res.body);
            }

            # issue #2507
            so = new SwaggerSchema("", b);
            {
                hash<RestRequestClientInfo> res = so.processRequest("post", "/api/b2507", {"a":"b"}, NOTHING, (MimeTypeJson,));
                assertEq("/api/b2507", res.uri_path);
                assertEq(MimeTypeJson, res.content);
                assertEq('{"a":"b"}', res.body);

                hash hdr = ("content-type": MimeTypeJson);
                hash<RestRequestServerInfo> sh = so.parseRequest("post", res.uri_path, res.body, \hdr);
                assertEq("/api/b2507", sh.path);
                assertEq(("a":"b"), sh.body);

                hdr = ("content-type": MimeTypeYamlRpc);
                assertThrows("DESERIALIZATION-ERROR", \so.parseRequest(), ("post", res.uri_path, res.body, \hdr));

                hdr = ("content-type": MimeTypeText);
                assertThrows("SCHEMA-VALIDATION-ERROR", \so.parseRequest(), ("post", res.uri_path, res.body, \hdr));

                hash<RestResponseClientInfo> rsh = so.parseResponse("post", res.uri_path, 200, "\"string\"", ("content-type": MimeTypeJson));
                assertEq(200, rsh.code);
                assertEq(MimeTypeJson, rsh.hdr."content-type");
                assertEq("string", rsh.body);
                assertThrows("DESERIALIZATION-ERROR", \so.parseResponse(), ("post", res.uri_path, 200, "\"string\"", ("content-type": MimeTypeYamlRpc)));
            }

            # issue #2517
            so = new SwaggerSchema("", b);
            {
                hash hdr = {
                    "http_version": "1.1",
                    "status_code": 200,
                    "status_message": "OK",
                    "server": "Qorus-HTTP-Server/0.3.11.1",
                    "connection": "Keep-Alive",
                    "date": "Thu, 30 Nov 2017 12:59:55 GMT",
                    "content-length": "0",
                };
                hash<RestResponseClientInfo> res = so.parseResponse("post", "/api/b2517", 200, NOTHING, hdr);
                assertEq(200, res.code);
                assertEq(NOTHING, res.body);
            }

            # issue #2516
            so = new SwaggerSchema("", b);
            {
                hash<RestRequestClientInfo> res = so.processRequest("post", "/api/b2517", <feedface>,
                    {"Content-Type": "image/jpeg"});
                assertEq("/api/b2517", res.uri_path);
                assertEq(<feedface>, res.body);

                # the following call would throw a SCHEMA-VALIDATION-ERROR without the fix
                res = so.processRequest("post", "api/b2517", <feedface>, {"Content-Type": "image/jpeg"});
                assertEq("/api/b2517", res.uri_path);
                assertEq(<feedface>, res.body);
            }

            # issue #4087
            so = new SwaggerSchema("", b + {"basePath": "/v1"});
            {
                assertEq("/v1", so.getBasePath());
                hash<RestRequestClientInfo> res = so.processRequest("post", "v1/api/b2517", <feedface>,
                    {"Content-Type": "image/jpeg"});
                assertEq("/v1/api/b2517", res.uri_path);
                assertEq(<feedface>, res.body);
                hash<RestResponseClientInfo> rsh = so.parseResponse("post", "/v1/api/b2507", 200, "\"string\"",
                    {"content-type": MimeTypeJson});
                assertEq(200, rsh.code);
                assertEq(MimeTypeJson, rsh.hdr."content-type");
                assertEq("string", rsh.body);
                assertThrows("SCHEMA-VALIDATION-ERROR", \so.parseResponse(), ("post", "/v2/api/b2507", 200,
                    "\"string\"", {"content-type": MimeTypeJson}));
            }
        }

        {
            string rep = makeBody("OK");

            HttpTestServer serv(rep);
            string url = sprintf("http://localhost:%d", serv.port);
            hash<auto> opts = {
                "url": url,
                "swagger": dirname(get_script_path()) + DirSep + "PetStore.swagger.yaml",
            };
            {
                on_exit serv.done();

                RestClient rc(opts);
                #printf("url: %y\n", url);
                hash<auto> h = rc.get("/user/login?username=user;password=pass");
                assertEq("OK", h.body);

                assertThrows("SCHEMA-VALIDATION-ERROR", \rc.get(), "/user/login?x=y");
                assertThrows("SCHEMA-VALIDATION-ERROR", \rc.get(), "/something?x=y");
            }
            {
                serv.restart();
                on_exit serv.done();

                RestClient rc(opts + {
                    "swagger_base_path": "/",
                });
                hash<auto> h = rc.get("/user/login?username=user;password=pass");
                assertEq("OK", h.body);
            }
            {
                serv.restart();
                on_exit serv.done();

                RestClient rc(opts + {
                    "swagger_base_path": "/v2",
                });
                rc.getValidator().setBasePath("v2");
                hash<auto> h = rc.get("/v2/user/login?username=user;password=pass");
                assertEq("OK", h.body);
            }
        }
    }

    private testValidation(SwaggerSchema so) {
        so.processRequest("get", "/user/login?username=user;password=pass");
        assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("get", "/user/login"));
        assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("post", "/pet?username=user"));
        assertThrows("SCHEMA-VALIDATION-ERROR", \so.processRequest(), ("get", "/user/login?username;password=pass"));

        so.parseResponse("get", "/user/login", 200, "\"string\"", ("content-type": MimeTypeJson));

        so.processRequest("get", "/pet/findByStatus?status=available");
        assertThrows("SCHEMA-VALIDATION-ERROR", "status.*happy", \so.processRequest(), ("get", "/pet/findByStatus?status=happy"));

        # test arrays in parameters
        {
            string uri = "/pet/findByTags?tags=tag1;tags=tag2";
            hash<RestRequestClientInfo> req = so.processRequest("GET", uri);
            assertEq(uri, req.uri_path);
        }

        {
            string uri = "/pet/findByTags?tags=tag1&tags=tag2";
            hash<RestRequestClientInfo> req = so.processRequest("GET", uri);
            assertEq(uri, req.uri_path);
        }

        {
            hash tag1 = (
                "id": 1,
                "name": "n1",
            );
            hash tag2 = (
                "id": 2,
                "name": "n2",
            );
            hash hdr = {};
            hash body = (
                "name": "Scooby",
                "photoUrls": ("str1",),
                "tags": (tag1, tag2),
            );
            hash<RestRequestClientInfo> req = so.processRequest("POST", "/pet", body, hdr);
            assertEq(MimeTypeJson, req.content);
            assertEq(Type::String, req.body.type());
            assertEq("/v2/pet", req.uri_path);
            assertEq("UTF-8", req.body.encoding());
            #printf("req: %N\n", req);

            {
                hdr = ("content-type": req.content);
                # strip base path for request
                req.uri_path =~ s/^\/v2//;
                hash<RestRequestServerInfo> sh = so.parseRequest("POST", req.uri_path, req.body, \hdr);
                assertEq("Scooby", sh.body.name);
                assertEq(("str1",), sh.body.photoUrls);
                assertEq((tag1, tag2), sh.body.tags);
                #printf("sh: %N\n", sh);
            }

            body += (
                "id": 100,
                "category": (
                    "id": 200,
                    "name": "Super",
                ),
                "status": "available",
            );
            req = so.processRequest("POST", "/pet", body, hdr);
            assertEq(MimeTypeJson, req.content);
            assertEq(Type::String, req.body.type());
            assertEq("/v2/pet", req.uri_path);
            assertEq("UTF-8", req.body.encoding());

            {
                hdr = ("content-type": req.content);
                # strip base path for request
                req.uri_path =~ s/^\/v2//;
                hash<RestRequestServerInfo> sh = so.parseRequest("POST", req.uri_path, req.body, \hdr);
                assertEq(100, sh.body.id);
                assertEq("Scooby", sh.body.name);
                assertEq(("str1",), sh.body.photoUrls);
                assertEq((tag1, tag2), sh.body.tags);
                assertEq("available", sh.body.status);
            }

            assertThrows("SERIALIZATION-ERROR", MimeTypeOctetStream, \so.processRequest(), ("POST", "/pet", body, hdr, MimeTypeOctetStream));

            delete body.photoUrls;
            assertThrows("SCHEMA-VALIDATION-ERROR", "missing.*photoUrls", \so.processRequest(), ("post", "/pet", body, hdr));
        }

        {
            string rep = makeBody("OK");

            HttpTestServer serv(rep);
            on_exit serv.done();

            string url = sprintf("http://localhost:%d", serv.port);
            hash opts = (
                "url": url,
                "validator": so,
            );

            assertEq("/v2", so.getBasePath());

            RestClient rc(opts);
            #printf("url: %y\n", url);
            hash h = rc.get("/user/login?username=user;password=pass");
            assertEq("OK", h.body);

            assertThrows("SCHEMA-VALIDATION-ERROR", \rc.get(), "/user/login?x=y");
            assertThrows("SCHEMA-VALIDATION-ERROR", \rc.get(), "/something?x=y");
        }

        {
            hash<HttpResponseInfo> res = so.processResponse("get", "/user/login", 200, "OK", NOTHING, MimeTypeJson);
            assertEq(200, res.code);
            assertEq(MimeTypeJson, res.hdr."Content-Type");
            assertEq("\"OK\"", res.body);
        }

        {
            # let's check our order for 400 of the same pet
            hash body = (
                "id": 1,
                "petId": 2,
                "quantity": 400,
                "shipDate": now_us(),
                "status": "approved",
                "complete": True,
            );
            hash<HttpResponseInfo> res = so.processResponse("post", "/store/order", 200, body, NOTHING, MimeTypeJson);
            assertEq(200, res.code);
            assertEq(MimeTypeJson, res.hdr."Content-Type");

            body.shipDate = "1990-01-01";
            res = so.processResponse("post", "/store/order", 200, body, NOTHING, MimeTypeJson);
            assertEq(200, res.code);
            assertEq(MimeTypeJson, res.hdr."Content-Type");

            body.shipDate = 200;
            assertThrows("SCHEMA-VALIDATION-ERROR", "shipDate", \so.processResponse(), ("post", "/store/order", 200, body, NOTHING, MimeTypeJson));
        }
    }

    testExamples() {
        checkModules();

        testExamples(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.yaml"));
        testExamples(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.json"));
    }

    private testExamples(SwaggerSchema so) {
        hash<RestQoreExampleCodeInfo> qex = so.getQoreExampleRequest("GET", "/user/login");
        assertEq(True, qex.example =~ /\/user\/login\?username=[a-z]+;password=[a-z]+/m);
        hash<RestExampleRequestInfo> rex = so.getExampleRequest("GET", "/user/login");
        assertEq(True, rex.request_uri =~ /\/user\/login\?username=[a-z]+;password=[a-z]+/m);

        qex = so.getQoreExampleRequest("POST", "/pet");
        assertEq(Type::String, qex.example.type());
        #printf("qex: %N\n", qex);

        rex = so.getExampleRequest("POST", "/pet");
        assertEq("POST /pet HTTP/1.1", rex.request_uri);
        assertEq(MimeTypeJson, rex.hdr."Content-Type");

        qex = so.getQoreExampleResponse("GET", "/user/login", 200);
        assertEq(True, qex.example =~ /makeResponse.200/);
        hash<RestExampleResponseInfo> res = so.getExampleResponse("GET", "/user/login", 200);
        assertEq(200, res.code);
        assertEq(MimeTypeJson, res.hdr."Content-Type");

        qex = so.getQoreExampleResponse("get", "/pet/{pet}", 200);
        assertEq(True, qex.example =~ /makeResponse.200/);
        res =  so.getExampleResponse("GET", "/pet/{pet}", 200);
        assertEq(200, res.code);
        assertEq(MimeTypeJson, res.hdr."Content-Type");
        assertEq(True, res.body =~ /id/);
        #printf("res: %N\n", res);
        #printf("qex: %N\n", qex);
        #printf("rex: %N\n", rex);
    }

    testRealMessages() {
        checkModules();

        testRealMessages(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.yaml"));
        testRealMessages(Swagger::SwaggerLoader::fromFile(get_script_dir() + DirSep + "PetStore.swagger.json"));
    }

    testRealMessages(SwaggerSchema so) {
        foreach hash h in (JsonRequestMessages.pairIterator()) {
            foreach hash mh in (h.value.pairIterator()) {
                hash hdr."content-type" = MimeTypeJson;
                hash<RestRequestServerInfo> sh = so.parseRequest(mh.key, h.key, mh.value, \hdr);
                assertEq(Type::Hash, sh.type());
            }
        }

        foreach hash h in (JsonResponseMessages.pairIterator()) {
            foreach hash mh in (h.value.pairIterator()) {
                foreach hash ch in (mh.value.pairIterator()) {
                    hash hdr."content-type" = MimeTypeJson;
                    hash<RestResponseClientInfo> sh = so.parseResponse(mh.key, h.key, ch.key.toInt(), ch.value, hdr);
                    assertEq(Type::Hash, sh.type());
                }
            }
        }
    }

    static string makeBody(any b) {
        string json = my_make_json(b);

        return sprintf("HTTP/1.1 200 OK\r\nServer: Test/1.0\r\nContent-Type: application/json\r\nContent-Length: %d\r\nDate: %s GMT\r\n\r\n%s", json.size(), gmtime().format("Dy, DD Mon YYYY HH:mm:SS"), json);
    }
}
