"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
exports.__esModule = true;
exports.genDatabaseResourceOpenApiDocs = exports.pathGenerator = exports.ServiceModels = exports.keyParams = exports.fieldsContext = exports.searchHeaders = exports.standardHeaders = void 0;
var change_case_1 = require("change-case");
var const_1 = require("../const");
var utils_1 = require("../utils");
var genSearchParams = function (oa3DataSchema, resource) {
    return function (_a) {
        var name = _a[0], _b = _a[1], type = _b.type, required = _b.required, keyComponent = _b.keyComponent, geoqueryType = _b.geoqueryType, softDeleteFlag = _b.softDeleteFlag, updateDisabled = _b.updateDisabled, createRequired = _b.createRequired, createDisabled = _b.createDisabled;
        return ({
            name: name,
            description: name,
            "in": "query",
            required: required,
            schema: oa3DataSchema({ resource: resource, name: name, type: type })
        });
    };
};
// TODO: eval these usages. complexqueries wont be implemented into GraphQL and OpenAPI until this is done
var parseComplexResources = function (str) {
    return str.includes(":") ? str.split(":")[0] : str;
};
exports.standardHeaders = {
    "x-request-id": {
        schema: {
            type: "string"
        },
        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
    },
    "x-sql": {
        schema: {
            type: "string"
        },
        description: "plaintext SQL used to complete db transaction."
    }
};
var NO_BODY_204 = {
    "204": {
        headers: __assign({}, exports.standardHeaders),
        description: "A NO BODY Response as DB does not support returning on CREATE or UPDATE"
    }
};
var requestHeaders = {
    "x-get-count": {
        "in": "header",
        schema: {
            type: "string"
        }
    },
    "x-get-sql": {
        "in": "header",
        schema: {
            type: "string"
        }
    }
};
var requestHeaderParams = Object.entries(requestHeaders).map(function (_a) {
    var name = _a[0], deff = _a[1];
    return (__assign(__assign({ name: name }, deff), { description: const_1.SEARCH_QUERY_CONTEXT_DESCRIPTION[name] || "" }));
});
exports.searchHeaders = __assign(__assign({}, exports.standardHeaders), { "x-count": {
        schema: {
            type: "string"
        },
        description: "full count of all records that meet query criteria (omitting any pagiation specs). useful for rendering pagiation limits without making another service call."
    } });
var contextNumbers = ["page", "limit"];
var genSearchContextParam = function (seperator) {
    if (seperator === void 0) { seperator = const_1.PIPE; }
    return function (key) { return ({
        name: "" + seperator + key,
        description: const_1.SEARCH_QUERY_CONTEXT_DESCRIPTION[key] || "query context: " + key,
        "in": "query",
        schema: { type: contextNumbers.includes(key) ? "number" : "string" }
    }); };
};
var searchContextParams = Object.keys(const_1.SEARCH_QUERY_CONTEXT).map(genSearchContextParam());
var searchSubContextParams = Object.keys(const_1.SEARCH_SUBQUERY_CONTEXT).map(genSearchContextParam("]"));
var searchComplexContextParams = __spreadArrays(searchContextParams, searchSubContextParams);
// almost certainly a better way to do this
exports.fieldsContext = searchContextParams.filter(function (_a) {
    var name = _a.name;
    return name === "|fields";
})[0];
exports.keyParams = function (fn) {
    return function (input, resource) {
        return input.map(function (_a) {
            var name = _a.field, type = _a.type;
            return ({
                name: name,
                description: name,
                "in": "query",
                required: true,
                schema: fn({ resource: resource, name: name, type: type })
            });
        });
    };
};
exports.ServiceModels = {
    _service_count: {
        type: "object",
        properties: {
            count: {
                type: "number"
            }
        }
    },
    _search_interfaces: {
        type: "object",
        // property type could be more specific
        properties: Object.keys(const_1.SUPPORTED_OPERATIONS).reduce(function (props, key) {
            var _a;
            return (__assign(__assign({}, props), (_a = {}, _a["field." + key] = { type: "string" }, _a)));
        }, {})
    },
    _debug_resource_response: {
        type: "object",
        properties: {
            payload: { type: "object" },
            context: { type: "object" },
            requestId: { type: "string" },
            searchQuery: { type: "object", nullable: true },
            hardDelete: { type: "boolean", nullable: true },
            sql: { type: "string" }
        }
    }
};
// SUPPORTED_OPERATIONS, DEFINED_ARG_LENGTHS
exports.pathGenerator = function (pathPrefix) { return function (path) { return "" + pathPrefix + path; }; };
exports.genDatabaseResourceOpenApiDocs = function (_a) {
    var db = _a.db, st = _a.st, logger = _a.logger, metadata = _a.metadata, debugMode = _a.debugMode, validators = _a.validators, dbResources = _a.dbResources, ResourceReports = _a.ResourceReports, supportsReturn = _a.supportsReturn, permissions = _a.permissions;
    return __awaiter(void 0, void 0, void 0, function () {
        var ResourceReportsObject, genPath, oa3DataSchema, dbDataTypetoOA3DataType, schemas, debugRecord, paths, contactName, contactEmail, contactUrl, servers, metaInfo, metaContact, metaServers, base, serviceRoutes;
        return __generator(this, function (_b) {
            ResourceReportsObject = Object.fromEntries(ResourceReports);
            genPath = exports.pathGenerator(metadata.routerPrefix);
            oa3DataSchema = function (_a) {
                var _resource = _a.resource, _name = _a.name, type = _a.type;
                var resource = parseComplexResources(_resource);
                var name = _name.startsWith(">") ? _name.replace(">", "") : _name;
                // need to handled cases like UUID, dates and other things that are populated in the DBs
                var nullable = dbResources[resource][name].notnull ? undefined : true;
                var newType = { type: type, nullable: nullable };
                if (type === "number") {
                    newType = __assign(__assign({}, newType), dbDataTypetoOA3DataType(dbResources[resource][name].type));
                }
                return newType;
            };
            dbDataTypetoOA3DataType = function (dbDataType) {
                switch (dbDataType) {
                    case "integer":
                        // ignore numeric and double precision here as they are now strings due to arbitrary precision
                        // case "numeric":
                        // case "double precision":
                        var type = dbDataType === "integer" ? "integer" : "number";
                        var format = dbDataType === "double precision" ? "double" : undefined;
                        return { type: type, format: format };
                    default:
                        return { type: dbDataType };
                }
            };
            schemas = __assign({}, exports.ServiceModels);
            debugRecord = {};
            paths = ResourceReports.reduce(function (record, _a) {
                var _b, _c;
                var resource = _a[0], _d = _a[1], create = _d.create, read = _d.read, update = _d.update, del = _d["delete"], search = _d.search;
                var isComplexResource = resource.includes(":");
                var subResource = isComplexResource
                    ? resource.split(":")[1]
                    : undefined;
                var subResourceReport = isComplexResource
                    ? ResourceReportsObject[resource.split(":")[1]]
                    : undefined;
                var searchEntries = Object.entries(search);
                var searchSubEntries = !isComplexResource
                    ? []
                    : Object.entries(subResourceReport.search).map(function (_a) {
                        var field = _a[0], info = _a[1];
                        return [">" + field, info];
                    });
                // need to append `subResource` keys with `>` prefixes here if `isComplexResource`
                // const searchEntriesParams = isComplexResource ? [...searchEntries, ...searchSubEntries] : searchEntries
                var keys = searchEntries.reduce(function (components, _a) {
                    var field = _a[0], _b = _a[1], type = _b.type, keyComponent = _b.keyComponent;
                    return __spreadArrays(components, (keyComponent ? [{ field: field, type: type }] : []));
                }, []);
                var Resource = change_case_1.pascalCase(resource);
                var allow = utils_1.permitted(permissions);
                var permit = {
                    create: allow(resource, "create"),
                    read: allow(resource, "read"),
                    update: allow(resource, "update"),
                    "delete": allow(resource, "delete"),
                    any: true
                };
                permit.any =
                    permit.create || permit.read || permit.update || permit["delete"];
                var pathResource = genPath(const_1.URL_ROOT_SERVICE + "/" + resource);
                var pathResourceRecord = pathResource + "/record";
                var parameters = __spreadArrays(searchEntries.map(genSearchParams(oa3DataSchema, resource)), (isComplexResource
                    ? searchSubEntries.map(genSearchParams(oa3DataSchema, subResource))
                    : []), (isComplexResource
                    ? searchComplexContextParams
                    : searchContextParams), requestHeaderParams);
                record[pathResource] = {
                    get: {
                        summary: "search " + resource,
                        operationId: "search" + Resource,
                        tags: [Resource],
                        parameters: parameters,
                        responses: {
                            "200": {
                                headers: __assign({}, exports.searchHeaders),
                                description: "A paged array of " + Resource + " Records",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "array",
                                            items: {
                                                $ref: "#/components/schemas/" + Resource
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    post: {
                        summary: "create " + resource,
                        operationId: "create" + Resource,
                        tags: [Resource],
                        parameters: [exports.fieldsContext],
                        requestBody: {
                            description: "Single " + Resource + " or array of " + Resource,
                            required: true,
                            content: {
                                "application/json": {
                                    schema: {
                                        type: "array",
                                        items: {
                                            $ref: "#/components/schemas/" + Resource
                                        }
                                    }
                                }
                            }
                        },
                        responses: {
                            "200": {
                                headers: __assign({}, exports.standardHeaders),
                                description: "A " + Resource + " Record",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "array",
                                            items: {
                                                $ref: "#/components/schemas/" + Resource
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                };
                if (!supportsReturn) {
                    record[pathResource].post.responses = NO_BODY_204;
                }
                // if can operate with record -- go ahead and list it
                if (permit.any) {
                    schemas[Resource] = {
                        type: "object",
                        // property type could be more specific
                        properties: __spreadArrays(record[pathResource].get.parameters).filter(function (prop) { return !prop.name.startsWith(const_1.PIPE) && prop["in"] !== "header"; }) // remove context keys
                            .reduce(function (props, _a) {
                            var _b;
                            var name = _a.name, schema = _a.schema;
                            return (__assign(__assign({}, props), (_b = {}, _b[name] = __assign({}, schema), _b)));
                        }, {})
                    };
                }
                if (!permit.read) {
                    delete record[pathResource].get;
                }
                if (!permit.create) {
                    delete record[pathResource].post;
                }
                if (keys.length) {
                    var keyComponentParams = exports.keyParams(oa3DataSchema)(keys, resource);
                    record[pathResourceRecord] = {};
                    record[pathResourceRecord].get = {
                        summary: "read " + resource,
                        operationId: "read" + Resource,
                        tags: [Resource],
                        parameters: __spreadArrays(keyComponentParams, [exports.fieldsContext]),
                        responses: {
                            "200": {
                                headers: __assign({}, exports.standardHeaders),
                                description: "A " + Resource + " Record",
                                content: {
                                    "application/json": {
                                        schema: {
                                            $ref: "#/components/schemas/" + Resource
                                        }
                                    }
                                }
                            }
                        }
                    };
                    record[pathResourceRecord].put = {
                        summary: "update " + resource,
                        operationId: "update" + Resource,
                        tags: [Resource],
                        parameters: __spreadArrays(keyComponentParams, [exports.fieldsContext]),
                        requestBody: {
                            description: "Single " + Resource,
                            required: true,
                            content: {
                                "application/json": {
                                    schema: {
                                        $ref: "#/components/schemas/" + Resource
                                    }
                                }
                            }
                        },
                        responses: {
                            "200": {
                                headers: __assign({}, exports.standardHeaders),
                                description: "A " + Resource + " Record",
                                content: {
                                    "application/json": {
                                        schema: {
                                            $ref: "#/components/schemas/" + Resource
                                        }
                                    }
                                }
                            }
                        }
                    };
                    if (!supportsReturn) {
                        record[pathResourceRecord].put.responses = NO_BODY_204;
                    }
                    record[pathResourceRecord]["delete"] = {
                        summary: "delete " + resource,
                        operationId: "delete" + Resource,
                        tags: [Resource],
                        parameters: __spreadArrays(keyComponentParams),
                        responses: {
                            "200": {
                                headers: __assign({}, exports.standardHeaders),
                                description: "A " + Resource + " Record",
                                content: {
                                    "application/json": {
                                        schema: {
                                            $ref: "#/components/schemas/_service_count"
                                        }
                                    }
                                }
                            }
                        }
                    };
                    if (!permit.read) {
                        delete record[pathResourceRecord].get;
                    }
                    if (!permit.update) {
                        delete record[pathResourceRecord].put;
                    }
                    if (!permit["delete"]) {
                        delete record[pathResourceRecord]["delete"];
                    }
                }
                // no create on views & materialized views
                var nonTableResources = ["view", "materialized view"];
                var nonTableResource = nonTableResources.includes(dbResources[resource][searchEntries[0][0]].resource_type);
                // seems open-api was configured to not publish routes for keyed URLS if no keys already. must confirm
                if (nonTableResource) {
                    delete record[pathResource].post;
                    var uniqueRecord = record[pathResourceRecord];
                    if (uniqueRecord) {
                        delete uniqueRecord.put;
                        delete uniqueRecord["delete"];
                    }
                }
                var debugResponses = (_b = {},
                    _b["200"] = {
                        description: "objects generated by the framework for inspection up until the DB call.",
                        content: (_c = {},
                            _c["application/json"] = {
                                schema: {
                                    $ref: "#/components/schemas/_debug_resource_response"
                                }
                            },
                            _c)
                    },
                    _b);
                if (!!debugMode) {
                    for (var _i = 0, _e = Object.entries(__assign({}, record)); _i < _e.length; _i++) {
                        var _f = _e[_i], url = _f[0], operations = _f[1];
                        var pathChunks = url.split("/");
                        var debugPath = __spreadArrays(pathChunks.slice(0, 2), [
                            const_1.DEBUG
                        ], pathChunks.slice(3)).join("/");
                        var resourceType = pathChunks[2];
                        // only want to generate these on the originals.
                        // this block is within a loop iterating over all paths
                        // and this block adds to the paths -- so we'll be processing our new outputs :-1:
                        if (resourceType !== const_1.DEBUG) {
                            debugRecord[debugPath] = {};
                            for (var _g = 0, _h = Object.keys(operations); _g < _h.length; _g++) {
                                var operation = _h[_g];
                                var _j = __assign({}, record[url][operation]), summary = _j.summary, operationId = _j.operationId, doc = __rest(_j, ["summary", "operationId"]);
                                debugRecord[debugPath][operation] = __assign(__assign({}, doc), { summary: "debug " + summary, operationId: "debug_" + operationId, responses: debugResponses });
                            }
                        }
                    }
                }
                return __assign(__assign({}, record), debugRecord);
            }, {});
            contactName = metadata.name, contactEmail = metadata.email, contactUrl = metadata.url, servers = metadata.servers, metaInfo = __rest(metadata, ["name", "email", "url", "servers"]);
            metaContact = {
                name: contactName,
                email: contactEmail,
                url: contactUrl
            };
            metaServers = servers.map(function (url) { return ({ url: url }); });
            base = {
                openapi: "3.0.0",
                info: __assign({ version: const_1.SERVICE_VERSION, title: "Some Service Name", description: "Super Early (not fully functional yet) description of service resources.", termsOfService: "http://swagger.io/terms/", contact: __assign({ name: "Joe Wingard", email: "open-source@joewingard.com", url: "https://github.com/sudowing/service-engine" }, metaContact) }, metaInfo),
                servers: metaServers.length
                    ? metaServers
                    : [{ url: "http://core-service" }]
            };
            serviceRoutes = {
                "/healthz": {
                    get: {
                        summary: "healthcheck resource",
                        operationId: "healthz",
                        tags: ["_service"],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "heathcheck resource",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "object"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "/schema": {
                    get: {
                        summary: "graphql schema txt",
                        operationId: "schema",
                        tags: ["_service"],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "definition of dynamically generated graphql schema."
                            }
                        }
                    }
                },
                "/proto": {
                    get: {
                        summary: "proto txt",
                        operationId: "proto",
                        tags: ["_service"],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "definition of dynamically generated .proto file."
                            }
                        }
                    }
                },
                "/openapi": {
                    get: {
                        summary: "openapi json",
                        operationId: "openapi",
                        tags: ["_service"],
                        parameters: [
                            {
                                name: "debug",
                                description: "include debug routes (disabled by default)",
                                "in": "query",
                                required: false,
                                schema: {
                                    type: "string"
                                }
                            },
                        ],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "A paged array of Account Records",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "object"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "/resources": {
                    get: {
                        summary: "resources available in service (resource & CRUD + Search Operations)",
                        operationId: "resources",
                        tags: ["_service"],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "blah blah blah",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "object"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "/db_resources": {
                    get: {
                        summary: "db resources available in service (tables, views, mat views)",
                        operationId: "dbResources",
                        tags: ["_service"],
                        parameters: [
                            {
                                name: "resource",
                                description: "name of single db resource (table/view/materialized view)",
                                "in": "query",
                                required: false,
                                schema: {
                                    type: "string"
                                }
                            },
                        ],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "A paged array of Account Records",
                                content: {
                                    "application/json": {
                                        schema: {
                                            type: "object"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "/db_resources/raw": {
                    get: {
                        summary: "db resources available in service",
                        operationId: "dbResourcesRaw",
                        tags: ["_service"],
                        responses: {
                            "200": {
                                headers: {
                                    "x-request-id": {
                                        schema: {
                                            type: "string"
                                        },
                                        description: "uuid issued to each request. Injected into all server logs. useful for debugging"
                                    }
                                },
                                description: "A paged array of Account Records"
                            }
                        }
                    }
                }
            };
            return [2 /*return*/, __assign({ components: { schemas: schemas }, paths: __assign(__assign({}, paths), serviceRoutes) }, base)];
        });
    });
};
//# sourceMappingURL=openapi.js.map