All files / parser DataParser.ts

68.42% Statements 39/57
52.27% Branches 23/44
100% Functions 3/3
69.09% Lines 38/55

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                    5x       3x 3x 3x 3x 3x                     3x 721x   103x   206x   103x     103x   103x 103x 103x 309x   3x 3x 3x   3x   306x   103x                         103x           3x 3x   3x                               3x 3x 103x 103x 412x   100x 100x 100x   100x   312x   103x   3x          
import type { z } from 'zod'
import { DataSchema, type StringOptions, type NumberOptions, type BooleanOptions } from '../schema/data'
import { DateGenerator, StringGenerator, BooleanGenerator, NumberGenerator, PersonGenerator } from '../generator'
import RNG from '../util/rng'
 
export default class DataParser {
  protected schema: z.infer<typeof DataSchema>
 
  constructor(schema: unknown) {
    // Parse to validate and capture defaults/coercions
    this.schema = DataSchema.parse(schema)
  }
 
  generate(): unknown {
    const rng = new RNG(this.schema.seed)
    const stringGen = new StringGenerator({ locale: this.schema.locale, seed: this.schema.seed })
    const booleanGen = new BooleanGenerator({ locale: this.schema.locale, seed: this.schema.seed })
    const numberGen = new NumberGenerator({ locale: this.schema.locale, seed: this.schema.seed })
    const personGen = new PersonGenerator({ locale: this.schema.locale, seed: this.schema.seed })
 
    type ObjectNode = { type: 'object'; properties?: Record<string, Node>; required?: string[] }
    type ArrayNode = { type: 'array'; items: Node; minItems?: number; maxItems?: number }
    type DateNode = { type: 'date'; format?: 'dateTime' | 'time' | 'date' }
    type IntNode = { type: 'integer' } & Partial<NumberOptions>
    type FloatNode = { type: 'float' } & Partial<NumberOptions>
    type BoolNode = { type: 'boolean' } & Partial<BooleanOptions>
    type StrNode = { type: 'string' } & Partial<StringOptions>
    type PersonNode = { type: 'person' } & Partial<{ value?: 'email' | 'prefix' | 'firstName' | 'lastName' | 'phone' | 'address'; fields?: ('email' | 'prefix' | 'firstName' | 'lastName' | 'phone' | 'address')[]; pattern?: string }>
    type Node = StrNode | BoolNode | IntNode | FloatNode | DateNode | ArrayNode | ObjectNode | PersonNode
    const generateValue = (node: Node): unknown => {
      switch (node.type) {
      case 'person':
        return personGen.generate(node)
      case 'string':
        return stringGen.generate(node)
      case 'boolean':
        return booleanGen.generate(node)
      case 'integer':
      case 'float':
        return numberGen.generate(node) as number
      case 'object': {
        const out: Record<string, unknown> = {}
        const props = node.properties ?? {}
        for (const [k, v] of Object.entries(props)) {
          if (k === '...') {
            // Object spreading: generate the value and spread its properties
            const spreadValue = generateValue(v as Node)
            Eif (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
              Object.assign(out, spreadValue as Record<string, unknown>)
            }
            continue
          }
          out[k] = generateValue(v as Node)
        }
        return out
      }
      case 'array': {
        const min = Math.max(0, node.minItems ?? 1)
        const max = Math.max(min, node.maxItems ?? Math.max(min, 3))
        const len = min === max ? min : rng.uniformInt(min, max)
        const items: unknown[] = []
        for (let i = 0; i < len; i++) {
          items.push(generateValue(node.items as Node))
        }
        return items
      }
      case 'date':
        return new DateGenerator({ locale: this.schema.locale, seed: this.schema.seed, refDate: this.schema.refDate }).generate(node)
      default:
        throw new Error('Unsupported type')
      }
    }
 
    const rootProps = (this.schema as unknown as { properties?: Record<string, unknown> }).properties ?? {}
    const count = this.schema.count ?? 1
    
    Iif (count === 1) {
      const result: Record<string, unknown> = {}
      for (const [key, value] of Object.entries(rootProps)) {
        if (key === '...') {
          // Object spreading at root level: generate the value and spread its properties
          const spreadValue = generateValue(value as Node)
          if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
            Object.assign(result, spreadValue as Record<string, unknown>)
          }
          continue
        }
        result[key] = generateValue(value as Node)
      }
      return result
    } else {
      // Generate multiple records
      const results: Record<string, unknown>[] = []
      for (let i = 0; i < count; i++) {
        const result: Record<string, unknown> = {}
        for (const [key, value] of Object.entries(rootProps)) {
          if (key === '...') {
            // Object spreading at root level: generate the value and spread its properties
            const spreadValue = generateValue(value as Node)
            Eif (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
              Object.assign(result, spreadValue as Record<string, unknown>)
            }
            continue
          }
          result[key] = generateValue(value as Node)
        }
        results.push(result)
      }
      return results
    }
  }
 
}