/**
 * Implements the logic for generating a response from a service file
 */ import jsfRequire = require ( "@meeshkanml/json-schema-faker" ) ; const jsf = jsfRequire . hasOwnProperty ( "default" ) ? jsfRequire . default : jsfRequire ; import * as jsonschema from "@meeshkanml/jsonschema" ; import { array } from "fp-ts/lib/Array" ; import { fold , isNone , none , Option , some } from "fp-ts/lib/Option" ; import { pipe } from "fp-ts/lib/pipeable" ; import { isParameter , isResponse , MediaType , RequestBody , } from "loas3/dist/generated/full" ; import { omit } from "lodash" ; import { fromTraversable , Getter , Iso , Lens , Optional , Prism , Traversal , } from "monocle-ts" ; import { allMethods , changeRef , changeRefs , getComponentFromRef , getParameterFromRef , getRequestBodyFromRef , getResponseFromRef , getSchemaFromRef , internalGetComponent , MethodNames , objectToArray , valueLens , } from "openapi-refinements" ; import * as url from "whatwg-url" ; import { IFakerOptions } from "./faker" ; import { CreateResponse , IListener , ISerializedRequest , ISerializedResponse , IUnmockOptions , } from "./interfaces" ; import { Header , isReference , OpenAPIObject , Operation , Parameter , PathItem , Reference , Response , Responses , Schema , } from "./service/interfaces" ; import { ServiceStore } from "./service/serviceStore" ; param protocol param host param o export const matchUrls = ( protocol , host , o ) : string [ ] => o . servers ? o . servers . map ( m => m . url ) . filter ( i => new url . URL ( i ) . host === host && new url . URL ( i ) . protocol === `${ protocol }:` ) : [ ] ; const schemaPrism = ( oai ) : Prism < Schema | Reference , Schema > => new Prism ( s => isReference ( s ) ? getSchemaFromRef ( oai , s . $ref . split ( "/" ) [ 3 ] ) : some ( s ) , a => a ) ; const makeDefinitionsFromSchema = ( o ) : Record < string , Schema > => o . components && o . components . schemas ? Object . entries ( o . components . schemas ) . reduce ( ( a , b ) => ( { ... a , [ b [ 0 ] ] : isReference ( b [ 1 ] ) ? changeRef ( b [ 1 ] ) : changeRefs ( b [ 1 ] ) , } ) , { } ) : { } ; const findRelevantPath = ( m , a , p ) : PathItem => a . length === 0 ? p : findRelevantPath ( m , a . slice ( 1 ) , a [ 0 ] === m ? p : omit ( p , a [ 0 ] ) ) ; param m param p export const getPathItemWithMethod = ( m , p ) : PathItem => findRelevantPath ( m , allMethods , p ) ; const getRequiredRequestQueryOrHeaderParametersInternal = ( header , t , oai , p ) : Parameter [ ] => t . composePrism ( new Prism ( s => isReference ( s ) ? getParameterFromRef ( oai , s . $ref . split ( "/" ) [ 3 ] ) : some ( s ) , a => a ) ) . composePrism ( new Prism ( s => s . in === ( header ? "header" : "query" ) && s . required ? some ( s ) : none , a => a ) ) . composeGetter ( new Getter ( i => i ) ) . getAll ( p ) . filter ( a => a . schema ? ! isNone ( schemaPrism ( oai ) . getOption ( a . schema ) ) : false ) . map ( b => ( { ... b , schema : b . schema ? isReference ( b . schema ) ? changeRef ( b . schema ) : changeRefs ( b . schema ) : { type : "string" } , } ) ) ; const getRequiredRequestQueryOrHeaderParameters = ( header , req , oai , p ) : Parameter [ ] => [ ... getRequiredRequestQueryOrHeaderParametersInternal ( header , Optional . fromNullableProp < PathItem > ( ) ( "parameters" ) . composeTraversal ( fromTraversable ( array ) ( ) ) , oai , p ) , ... getRequiredRequestQueryOrHeaderParametersInternal ( header , Optional . fromNullableProp < PathItem > ( ) ( req . method ) . composeOptional ( Optional . fromNullableProp < Operation > ( ) ( "parameters" ) ) . composeTraversal ( fromTraversable ( array ) ( ) ) , oai , p ) , ] ; const getRequiredRequestBodySchemas = ( req , oai , p ) : Schema [ ] => Optional . fromNullableProp < PathItem > ( ) ( req . method ) . composeOptional ( Optional . fromNullableProp < Operation > ( ) ( "requestBody" ) ) . composePrism ( new Prism ( s => isReference ( s ) ? getRequestBodyFromRef ( oai , s . $ref . split ( "/" ) [ 3 ] ) : some ( s ) , a => a ) ) . composeOptional ( Optional . fromNullableProp < RequestBody > ( ) ( "content" ) ) . composeIso ( objectToArray ( ) ) . composeTraversal ( fromTraversable ( array ) ( ) ) . composeLens ( valueLens ( ) ) . composeOptional ( Optional . fromNullableProp < MediaType > ( ) ( "schema" ) ) . composePrism ( schemaPrism ( oai ) ) . composeGetter ( new Getter ( i => i ) ) . getAll ( p ) ; const keepMethodIfRequiredRequestBodyIsPresent = ( req , oai ) => ( p ) : PathItem => ! p [ req . method ] ? p : pipe ( getRequiredRequestBodySchemas ( req , oai , p ) . filter ( i => ! jsonschema . validate ( req . bodyAsJson , { ... changeRefs ( i ) , definitions : makeDefinitionsFromSchema ( oai ) , } ) . valid ) . length === 0 ? some ( p ) : none , fold ( ( ) => omit ( p , req . method ) , a => a ) ) ; const keepMethodIfRequiredHeaderParametersArePresent = ( req , oai ) => ( p ) => keepMethodIfRequiredQueryOrHeaderParametersArePresent ( true , req , oai , p ) ; const keepMethodIfRequiredQueryPrametersArePresent = ( req , oai ) => ( p ) => keepMethodIfRequiredQueryOrHeaderParametersArePresent ( false , req , oai , p ) ; const keepMethodIfRequiredQueryOrHeaderParametersArePresent = ( header , req , oai , p ) : PathItem => ! p [ req . method ] ? p : pipe ( getRequiredRequestQueryOrHeaderParameters ( header , req , oai , p ) , properties => jsonschema . validate ( header ? req . headers || { } : req . query , { type : "object" , properties : properties . reduce ( ( a , b ) => ( { ... a , [ b . name ] : b . schema } ) , { } ) , required : properties . filter ( i => i . required ) . map ( i => i . name ) , additionalProperties : true , definitions : makeDefinitionsFromSchema ( oai ) , } ) . valid ? some ( p ) : none , fold ( ( ) => omit ( p , req . method ) , a => a ) ) ; const maybeAddStringSchema = ( s ) : Array < Reference | Schema > => ( s . length === 0 ? [ { type : "string" } ] : s ) ; const discernName = ( o , n ) : Option < Parameter > => isNone ( o ) || ( o . value . name === n && o . value . in === "path" ) ? o : none ; const internalGetParameter = ( t , vname , pathItem , oas ) => t . composePrism ( new Prism ( i => isReference ( i ) ? discernName ( getParameterFromRef ( oas , refName ( i ) ) , vname ) : discernName ( some ( i ) , vname ) , a => a ) ) . composeOptional ( Optional . fromNullableProp < Parameter > ( ) ( "schema" ) ) . composeGetter ( identityGetter ( ) ) . getAll ( pathItem ) ; const pathItemPathParameter = ( vname , pathItem , oas ) => internalGetParameter ( Optional . fromNullableProp < PathItem > ( ) ( "parameters" ) . composeTraversal ( fromTraversable ( array ) < Reference | Parameter > ( ) ) , vname , pathItem , oas ) ; const operationPathParameter = ( vname , pathItem , operation , oas ) => internalGetParameter ( Optional . fromNullableProp < PathItem > ( ) ( operation ) . composeOptional ( Optional . fromNullableProp < Operation > ( ) ( "parameters" ) ) . composeTraversal ( fromTraversable ( array ) < Reference | Parameter > ( ) ) , vname , pathItem , oas ) ; const getMatchingParameters = ( vname , pathItem , operation , oas ) => maybeAddStringSchema ( [ ... pathItemPathParameter ( vname , pathItem , oas ) , ... operationPathParameter ( vname , pathItem , operation , oas ) , ] ) ; param maybe const maybeJson = ( maybe ) => { try { return JSON . parse ( maybe ) ; } catch { return maybe ; } } ; param part param vname param pathItem param operation param oas const pathParameterMatch = ( part , vname , pathItem , operation , oas ) => getMatchingParameters ( vname , pathItem , operation , oas ) . filter ( i => ! [ ... new Set ( [ part , maybeJson ( part ) ] ) ] . reduce ( ( a , b ) => a || jsonschema . validate ( b , { ... i , definitions : makeDefinitionsFromSchema ( oas ) , } ) . valid , false ) ) . length === 0 ; const pathParamRegex = new RegExp ( /^\{[^}]+\}/gi ) ; export const matchesInternal = ( path , pathItemKey , pathItem , operation , o ) : boolean => path . length === pathItemKey . length && ( path . length === 0 || ( ( path [ 0 ] === pathItemKey [ 0 ] || ( pathItemKey [ 0 ] . match ( pathParamRegex ) !== null && pathParameterMatch ( path [ 0 ] , pathItemKey [ 0 ] . slice ( 1 , - 1 ) , pathItem , operation , o ) ) ) && matchesInternal ( path . slice ( 1 ) , pathItemKey . slice ( 1 ) , pathItem , operation , o ) ) ) ; param path param pathItemKey export const matches = ( path , pathItemKey , pathItem , method , oas ) : boolean => matchesInternal ( path . split ( "/" ) . filter ( i => i !== "" ) , pathItemKey . split ( "/" ) . filter ( i => i !== "" ) , pathItem , method , oas ) ; param r export const refName = ( r ) : string => r . $ref . split ( "/" ) [ 3 ] ; /**
 * An optional that returns `some` first element of an array
 * if it exists, or `none` if it doesn't.
 */ export const firstElementOptional = < T > ( ) => new Optional < T [ ] , T > ( s => ( s . length > 0 ? some ( s [ 0 ] ) : none ) , a => s => [ a , ... s . slice ( 1 ) ] ) ; /**
 * A lens that zooms into the `key` of a `[key, value]` pair.
 */ export const keyLens = < A , T > ( ) => new Lens < [ A , T ] , A > ( a => a [ 0 ] , a => s => [ a , s [ 1 ] ] ) ; const getFirstMethodInternal2 = ( p , n , m , o ? ) : Option < [ MethodNames , Operation ] > => o ? some ( [ n , o ] ) : getFirstMethodInternal ( m , p ) ; const getFirstMethodInternal = ( m , p ) : Option < [ MethodNames , Operation ] > => m . length === 0 ? none : getFirstMethodInternal2 ( p , m [ 0 ] , m . slice ( 1 ) , p [ m [ 0 ] ] ) ; export const getFirstMethod = ( p ) : Option < [ MethodNames , Operation ] > => getFirstMethodInternal ( allMethods , p ) ; param p export const operationOptional = new Optional < PathItem , [ MethodNames , Operation ] > ( a => getFirstMethod ( a ) , a => s => ( { ... s , [ a [ 0 ] ] : a [ 1 ] } ) ) ; param o param d export const getHeaderFromRef = ( o , d ) : Option < Header > => getComponentFromRef ( o , d , a => ( a . headers ? some ( a . headers ) : none ) , internalGetHeaderFromRef ) ; export const internalGetHeaderFromRef = internalGetComponent ( getHeaderFromRef ) ; export const useIfHeaderLastMile = ( p , r ) : Option < [ string , Schema ] > => isNone ( r ) ? none : some ( [ p . name , r . value || { type : "string" } ] ) ; param o param p export const useIfHeader = ( o , p ) : Option < [ string , Schema ] > => p . in !== "header" ? none : useIfHeaderLastMile ( p , p . schema ? isReference ( p . schema ) ? getSchemaFromRef ( o , refName ( p . schema ) ) : some ( p . schema ) : some ( { type : "string" } ) ) ; export const identityGetter = < T > ( ) => new Getter < T , T > ( i => i ) ; export const parameterSchema = ( o ) => new Optional < Parameter , [ string , Schema ] > ( a => useIfHeader ( o , a ) , a => s => ( { ... s , name : a [ 0 ] , schema : a [ 1 ] } ) ) ; const cutPath = ( paths , path ) : string => paths . length === 0 ? path : path . slice ( 0 , paths [ 0 ] . length ) === paths [ 0 ] ? path . slice ( paths [ 0 ] . length ) : cutPath ( paths . slice ( 1 ) , path ) ; const removeTrailingSlash = ( s ) => s . length === 0 ? s : s . slice ( - 1 ) === "/" ? s . slice ( 0 , - 1 ) : s ; param path param o param i export const truncatePath = ( path , o , i ) => cutPath ( matchUrls ( i . protocol , i . host , o ) . map ( u => removeTrailingSlash ( new url . URL ( u ) . pathname ) ) , path ) ; param req param r export const matcher = ( req , r ) : Record < string , OpenAPIObject > => objectToArray < OpenAPIObject > ( ) . composeTraversal ( fromTraversable ( array ) ( ) ) . composeLens ( valueLens ( ) ) . modify ( oai => Optional . fromNullableProp < OpenAPIObject > ( ) ( "paths" ) . composeIso ( objectToArray < PathItem > ( ) ) . composeTraversal ( fromTraversable ( array ) ( ) ) . composeLens ( valueLens ( ) ) . modify ( pathItem => pipe ( getPathItemWithMethod ( req . method , pathItem ) , keepMethodIfRequiredHeaderParametersArePresent ( req , oai ) , keepMethodIfRequiredQueryPrametersArePresent ( req , oai ) , keepMethodIfRequiredRequestBodyIsPresent ( req , oai ) ) ) ( { ... oai , ... ( oai . paths ? { paths : Object . entries ( oai . paths ) . reduce ( ( i , [ n , o ] ) => ( { ... i , ... ( matches ( truncatePath ( req . pathname , oai , req ) , n , o , req . method , oai ) ? { [ n ] : o } : { } ) , } ) , { } ) , } : { } ) , } ) ) ( Object . entries ( r ) . reduce ( ( i , [ n , o ] ) => ( { ... i , ... ( matchUrls ( req . protocol , req . host , o ) . length > 0 ? { [ n ] : o } : { } ) , } ) , { } ) ) ; param f export const hoistTransformer = ( f , name ) => ( req , r ) : Record < string , OpenAPIObject > => objectToArray < OpenAPIObject > ( ) . composeTraversal ( fromTraversable ( array ) < [ string , OpenAPIObject ] > ( ) . filter ( ( [ inName , o ] ) => inName === name && matchUrls ( req . protocol , req . host , o ) . length > 0 ) ) . composeLens ( valueLens ( ) ) . modify ( oai => f ( { ... req , path : truncatePath ( req . path , oai , req ) , pathname : truncatePath ( req . pathname , oai , req ) , } , oai ) ) ( r ) ; const toSchemas = objectToArray < OpenAPIObject > ( ) . composeOptional ( firstElementOptional ( ) ) ; const toSchema = toSchemas . composeLens ( valueLens ( ) ) ; param schemaRecord const drillDownToOperation = ( schemaRecord ) => toSchema . composeOptional ( Optional . fromNullableProp < OpenAPIObject > ( ) ( "paths" ) ) . composeIso ( objectToArray ( ) ) . composeOptional ( firstElementOptional ( ) ) . composeLens ( valueLens ( ) ) . composeOptional ( operationOptional ) . composeLens ( valueLens ( ) ) . getOption ( schemaRecord ) ; param schema param operation const headersFromOperation = ( schema , operation ) => Optional . fromNullableProp < Operation > ( ) ( "parameters" ) . composeTraversal ( fromTraversable ( array ) ( ) ) . composePrism ( new Prism ( s => isParameter ( s ) ? some ( s ) : isReference ( s ) ? getParameterFromRef ( schema , refName ( s ) ) : none , a => a ) ) . composeOptional ( parameterSchema ( schema ) ) . composeGetter ( identityGetter ( ) ) . getAll ( operation ) ; param schema param code const makeLensToResponseStartingFromOperation = ( schema , code ) => Optional . fromNullableProp < Operation > ( ) ( "responses" ) . composeOptional ( Optional . fromNullableProp < Responses > ( ) ( code ) ) . composePrism ( new Prism ( s => isResponse ( s ) ? some ( s ) : isReference ( s ) ? getResponseFromRef ( schema , refName ( s ) ) : none , a => a ) ) ; const stringHeader = ( n , o ) : Option < [ string , Header ] > => isNone ( o ) ? none : some ( [ n , o . value ] ) ; param schema param operation param code const headersFromResponse = ( schema , operation , code ) => makeLensToResponseStartingFromOperation ( schema , code ) . composeOptional ( Optional . fromNullableProp < Response > ( ) ( "headers" ) ) . composeIso ( objectToArray ( ) ) . composeTraversal ( fromTraversable ( array ) ( ) ) . composePrism ( new Prism < [ string , Reference | Header ] , [ string , Header ] > ( a => isReference ( a [ 1 ] ) ? stringHeader ( a [ 0 ] , getHeaderFromRef ( schema , refName ( a [ 1 ] ) ) ) : some ( [ a [ 0 ] , a [ 1 ] ] ) , a => a ) ) . composeIso ( new Iso < [ string , Header ] , Parameter > ( a => ( { ... a [ 1 ] , name : a [ 0 ] , in : "header" } ) , a => [ a . name , a ] ) ) . composeOptional ( parameterSchema ( schema ) ) . composeGetter ( identityGetter ( ) ) . getAll ( operation ) ; const lensToMimeType = ( schema , code ) => makeLensToResponseStartingFromOperation ( schema , code ) . composeOptional ( Optional . fromNullableProp < Response > ( ) ( "content" ) ) . composeIso ( objectToArray ( ) ) . composeOptional ( firstElementOptional ( ) ) ; param schema param operation param code const bodyFromResponse = ( schema , operation , code ) => lensToMimeType ( schema , code ) . composeLens ( valueLens ( ) ) . composeOptional ( Optional . fromNullableProp < MediaType > ( ) ( "schema" ) ) . getOption ( operation ) ; param schema param operation param code const mimeTypeFromResponse = ( schema , operation , code ) => lensToMimeType ( schema , code ) . composeLens ( keyLens ( ) ) . getOption ( operation ) ; export function responseCreatorFactory ( { listeners = [ ] , options , fakerOptions , store , } ) { return ( req ) => { const transformers = [ matcher , ... Object . values ( store . cores ) . map ( core => hoistTransformer ( core . transformer , core . name ) ) , ] ; const schemas = Object . entries ( store . cores ) . reduce ( ( a , [ n , x ] ) => ( { ... a , [ n ] : x . schema } ) , { } ) ; const schemaRecord = transformers . reduce ( ( a , b ) => b ( req , a ) , schemas ) ; const schema = toSchema . getOption ( schemaRecord ) ; const operation = drillDownToOperation ( schemaRecord ) ; if ( isNone ( operation ) || isNone ( schema ) ) { throw Error ( `Cannot find a matcher for this request: ${ JSON . stringify ( req , null , 2 ) }` ) ; } const codes = Object . keys ( operation . value . responses ) as Array < keyof Responses > ; const code = codes [ Math . floor ( Math . random ( ) * codes . length ) ] ; const statusCode = code === "default" ? 500 : + code ; const definitions = makeDefinitionsFromSchema ( schema . value ) ; const operationLevelHeaders = headersFromOperation ( schema . value , operation . value ) ; const responseLevelHeaders = headersFromResponse ( schema . value , operation . value , code ) ; const mimeType = mimeTypeFromResponse ( schema . value , operation . value , code ) ; const bodySchema = bodyFromResponse ( schema . value , operation . value , code ) ; const headerProperties = { ... [ ... operationLevelHeaders , ... responseLevelHeaders ] . reduce ( ( a , b ) => ( { ... a , [ b [ 0 ] ] : b [ 1 ] } ) , { } ) , } ; /**
         * Ensure that every generation starts from the same seed if not randomizing.
         */ if ( ! options . randomize ( ) ) { fakerOptions . randomNumberGenerator . restore ( ) ; } const res = generateMockFromTemplate ( { fakerOptions , statusCode , mimeType : isNone ( mimeType ) ? undefined : mimeType . value , headerSchema : { definitions , type : "object" , properties : headerProperties , required : Object . keys ( headerProperties ) , } , bodySchema : isNone ( bodySchema ) ? undefined : { definitions , ... ( isReference ( bodySchema . value ) ? changeRef ( bodySchema . value ) : changeRefs ( bodySchema . value ) ) , } , } ) ; const serviceName = toSchemas . composeLens ( keyLens ( ) ) . getOption ( schemaRecord ) ; if ( ! isNone ( serviceName ) && store . cores [ serviceName . value ] ) { store . cores [ serviceName . value ] . track ( { req , res } ) ; } listeners . forEach ( ( listener ) => listener . notify ( { req , res } ) ) ; jsf . reset ( ) ; return res ; } ; } const isNonEnumString = ( s ) => s . type === "string" && ! s . enum ; const chop = ( s ) => s . substring ( 1 , s . length - 2 ) ; const generateMockFromTemplate = ( { fakerOptions , statusCode , headerSchema , bodySchema , mimeType , } ) : ISerializedResponse => { jsf . extend ( "faker" , ( ) => require ( "faker" ) ) ; jsf . option ( "optionalsProbability" , fakerOptions . optionalsProbability ) ; jsf . option ( "fixedProbabilities" , fakerOptions . optionalsProbability === 1 ) ; jsf . option ( "failOnInvalidFormat" , false ) ; jsf . option ( "useDefaultValue" , false ) ; jsf . option ( "random" , ( ) => fakerOptions . randomNumberGenerator . get ( ) ) ; const bodyAsJson = bodySchema && mimeType && mimeType . indexOf ( "application/json" ) !== - 1 ? jsf . generate ( bodySchema ) : undefined ; const body = bodyAsJson ? JSON . stringify ( bodyAsJson ) : bodySchema && mimeType && isNonEnumString ( bodySchema ) ? chop ( jsf . generate ( bodySchema ) ) : bodySchema && mimeType ? jsf . generate ( bodySchema ) : undefined ; jsf . option ( "useDefaultValue" , true ) ; const resHeaders = headerSchema ? jsf . generate ( headerSchema ) : undefined ; jsf . option ( "useDefaultValue" , false ) ; if ( mimeType ) { resHeaders [ "Content-Type" ] = mimeType ; } return { body , bodyAsJson , headers : resHeaders , statusCode , } ; } ;