All files / src/transform crud.ts

97.65% Statements 83/85
98.97% Branches 96/97
92.86% Functions 13/14
98.65% Lines 73/74

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160  2x 2x               2x   2x 55x 5x   2x   52x       2x 20x 2x 18x 1x 17x   17x 16x     1x     2x 25x 1x           24x 24x 1x           23x   2x 23x 6x 5x 5x 1x           4x 1x               21x     2x 60x 60x 12x 9x       60x   125x 120x 24x 26x 26x   26x   25x   25x   25x               25x 100x                     25x 25x   25x 25x   23x 23x   21x 21x 20x 20x       21x 20x 20x     21x 20x     21x 20x   20x     21x 20x 20x     21x     2x  
import { ValueType, ObjectType, StringType } from 'yaschva'
import { map } from 'microtil'
import { validate, isValidationError } from './jsonSchema.js'
import {
  CrudContract, CrudAuthAll, CrudAuthSome, OutputSuccess, Output, ManageableFields
} from './types.js'
import {
  HttpMethods,
  SearchTypes
} from 'declarapi-runtime'
import { loadJSON, baseSchemaLocation } from '../util.js'
 
const contractOptions = (input: ValueType | ValueType[]): ValueType[] => {
  if (Array.isArray(input)) {
    if (input.some(x => x === '?')) { return input }
 
    return input.concat(['?'])
  }
  return [input, '?']
}
 
const searchToType =
  (idType: 'string'| StringType, dataType: ObjectType, search?: SearchTypes): ObjectType => {
    if (search === 'idOnly') {
      return { id: [idType, { $array: idType }, '?'] }
    } else if (search === 'textSearch') {
      return { search: ['string', '?'], id: [idType, { $array: idType }, '?'] }
    } else Iif (search === 'full') {
      return map(dataType, value => contractOptions(value))
    } else if (!search) {
      return {}
    }
 
    return search
  }
 
const checkIdField = (contract:CrudContract) :Output | false => {
  if (contract.dataType.id === undefined) {
    return {
      type: 'error',
      errors: 'id field does not exist in the data declaration'
    }
  }
 
  const idType: any = contract.dataType.id
  if (!(idType === 'string' || idType.$string)) {
    return {
      type: 'error',
      errors: 'Type of id field must be string'
    }
  }
 
  return false
}
const checkManageFields = (contract: CrudContract): Output|false => {
  for (const [key, value] of Object.entries(contract.manageFields || {})) {
    if (value) {
      const fieldType: any = contract.dataType[key]
      if (fieldType === undefined) {
        return {
          type: 'error',
          errors: `managed field "${key}" is not present on data type`
        }
      }
 
      if (!(fieldType === 'string' || fieldType.$string)) {
        return {
          type: 'error',
          errors: `managed field "${key}" must be a string, current type :${fieldType}`
        }
      }
    }
  }
 
  return false
}
 
const removeManaged = (args: ObjectType, manageFields?: ManageableFields):ObjectType => {
  const result = { ...args }
  for (const [key, value] of Object.entries(manageFields || {})) {
    if (value) {
      delete result[key]
    }
  }
 
  return result
}
const isCrudAuth = (tbd: any): tbd is CrudAuthAll => tbd.post !== undefined
const isCrudAuthSome = (tbd: any): tbd is CrudAuthSome => tbd.modify !== undefined
const transformForPost = (tbd: any) => Array.isArray(tbd) && tbd.find(x => x.createdBy) ? true : tbd
export const transform = async (data:CrudContract | any): Promise<Output> => {
  const valid = await validate(await loadJSON(`${await baseSchemaLocation()}crudContractSchema.json`), data)
 
  if (isValidationError(valid)) return valid
 
  const contractData: CrudContract = data
 
  const au = contractData.authentication
 
  const auth = {
    GET: isCrudAuth(au) ? au.get : (isCrudAuthSome(au) ? au.get : au),
    POST: isCrudAuth(au) ? au.post : transformForPost((isCrudAuthSome(au) ? au.modify : au)),
    PUT: isCrudAuth(au) ? au.put : (isCrudAuthSome(au) ? au.modify : au),
    PATCH: isCrudAuth(au) ? au.put : (isCrudAuthSome(au) ? au.modify : au),
    DELETE: isCrudAuth(au) ? au.delete : (isCrudAuthSome(au) ? au.delete || au.modify : au)
  }
 
  const createOutput = (method: HttpMethods, args: ObjectType,
    returns: ObjectType = contractData.dataType): OutputSuccess => ({
    method,
    name: contractData.name,
    authentication: auth[method],
    manageFields: contractData.manageFields || {},
    search: contractData.search,
    preferredImplementation: contractData.preferredImplementation,
    arguments: args,
    returns
  })
 
  const returnArray = { $array: contractData.dataType }
  const output: OutputSuccess[] = []
 
  const errorWithId = checkIdField(contractData)
  if (errorWithId) return errorWithId
 
  const errorWithManageFields = checkManageFields(contractData)
  if (errorWithManageFields) return errorWithManageFields
 
  const idType: any = contractData.dataType.id
  if (contractData.methods?.get !== false) {
    const search = contractData.search
    output.push(createOutput('GET',
      searchToType(idType, contractData.dataType, search), returnArray))
  }
 
  if (contractData.methods?.post !== false) {
    const post = { ...contractData.dataType, id: [idType, '?'] }
    output.push(createOutput('POST', removeManaged(post, contractData.manageFields)))
  }
 
  if (contractData.methods?.put !== false) {
    output.push(createOutput('PUT', removeManaged(contractData.dataType, contractData.manageFields)))
  }
 
  if (contractData.methods?.patch !== false) {
    const patch: {[s: string]: ValueType | ValueType[];} =
    { ...map(contractData.dataType, contractOptions), id: idType }
    output.push(createOutput('PATCH', removeManaged(patch, contractData.manageFields)))
  }
 
  if (contractData.methods?.delete !== false) {
    const deleteIds: {[s: string]: ValueType[];} = { id: [idType, { $array: idType }] }
    output.push(createOutput('DELETE', deleteIds, returnArray))
  }
 
  return { type: 'result', key: contractData.name, results: output }
}
 
export default transform