-- -- Licensed to the Apache Software Foundation (ASF) under one or more -- contributor license agreements. See the NOTICE file distributed with -- this work for additional information regarding copyright ownership. -- The ASF licenses this file to You under the Apache License, Version 2.0 -- (the "License"); you may not use this file except in compliance with -- the License. You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- local core = require("apisix.core") local xml2lua = require("xml2lua") local json = core.json local math = math local ngx = ngx local ngx_re = ngx.re local pairs = pairs local string = string local table = table local type = type local support_content_type = { ["application/xml"] = true, ["application/json"] = true, ["text/plain"] = true, ["text/html"] = true, ["text/xml"] = true } local schema = { type = "object", properties = { -- specify response delay time,default 0ms delay = { type = "integer", default = 0 }, -- specify response status,default 200 response_status = { type = "integer", default = 200, minimum = 100 }, -- specify response content type, support application/xml, text/plain -- and application/json, default application/json content_type = { type = "string", default = "application/json;charset=utf8" }, -- specify response body. response_example = { type = "string" }, -- specify response json schema, if response_example is not nil, this conf will be ignore. -- generate random response by json schema. response_schema = { type = "object" }, with_mock_header = { type = "boolean", default = true }, response_headers = { type = "object", minProperties = 1, patternProperties = { ["^[^:]+$"] = { oneOf = { { type = "string" }, { type = "number" } } } }, } }, anyOf = { { required = { "response_example" } }, { required = { "response_schema" } } } } local _M = { version = 0.1, priority = 10900, name = "mocking", schema = schema, } local function parse_content_type(content_type) if not content_type then return "" end local m = ngx_re.match(content_type, "([ -~]*);([ -~]*)", "jo") if m and #m == 2 then return m[1], m[2] end return content_type end function _M.check_schema(conf) local ok, err = core.schema.check(schema, conf) if not ok then return false, err end local typ = parse_content_type(conf.content_type) if not support_content_type[typ] then return false, "unsupported content type!" end return true end local function gen_string(example) if example and type(example) == "string" then return example end local n = math.random(1, 10) local list = {} for i = 1, n do table.insert(list, string.char(math.random(97, 122))) end return table.concat(list) end local function gen_number(example) if example and type(example) == "number" then return example end return math.random() * 10000 end local function gen_integer(example) if example and type(example) == "number" then return math.floor(example) end return math.random(1, 10000) end local function gen_boolean(example) if example and type(example) == "boolean" then return example end local r = math.random(0, 1) if r == 0 then return false end return true end local gen_array, gen_object, gen_by_property function gen_array(property) local output = {} if property.items == nil then return nil end local v = property.items local n = math.random(1, 3) for i = 1, n do table.insert(output, gen_by_property(v)) end return output end function gen_object(property) local output = {} if not property.properties then return output end for k, v in pairs(property.properties) do output[k] = gen_by_property(v) end return output end function gen_by_property(property) local typ = string.lower(property.type) local example = property.example if typ == "array" then return gen_array(property) end if typ == "object" then return gen_object(property) end if typ == "string" then return gen_string(example) end if typ == "number" then return gen_number(example) end if typ == "integer" then return gen_integer(example) end if typ == "boolean" then return gen_boolean(example) end return nil end function _M.access(conf, ctx) local response_content = "" if conf.response_example then response_content = conf.response_example else local output = gen_object(conf.response_schema) local typ = parse_content_type(conf.content_type) if typ == "application/xml" or typ == "text/xml" then response_content = xml2lua.toXml(output, "data") elseif typ == "application/json" or typ == "text/plain" then response_content = json.encode(output) else core.log.error("json schema body only support xml and json content type") end end ngx.header["Content-Type"] = conf.content_type if conf.with_mock_header then ngx.header["x-mock-by"] = "APISIX/" .. core.version.VERSION end if conf.response_headers then for key, value in pairs(conf.response_headers) do value = core.utils.resolve_var(value, ctx.var) core.response.add_header(key, value) end end if conf.delay > 0 then ngx.sleep(conf.delay) end return conf.response_status, core.utils.resolve_var(response_content, ctx.var) end return _M