packages feed

jsonlogic-0.1.0.0: test/Generator/Data.hs

{-# LANGUAGE OverloadedLists #-}

module Generator.Data where

import Data.Map as M (fromList, insert)
import Generator.Generic
  ( genGenericJsonBool,
    genGenericJsonNumber,
    genGenericJsonString,
    genGenericNonEmptyJsonString,
  )
import Generator.Utils (genUnbalancedSizeList)
import Hedgehog (Gen, Size (Size))
import Hedgehog.Gen (choice)
import JsonLogic.Json (Json (..), JsonObject)
import Text.Read (readMaybe)

-- | Inserts Json into a specific path and returns it
-- If it is arrived at the end of the path then it gives the value back
insertAtPath ::
  -- | The path at which to store the json
  [String] ->
  -- | Json data that needs to get inserted
  Json ->
  -- | Json object that needs to get updated
  Json ->
  -- | Updated Json object with inserted Json
  Json
-- End of path reached
insertAtPath [] value _ = value
-- Insert value to the map
insertAtPath (p : ps) value (JsonObject o) = case readMaybe p of
  Nothing -> JsonObject $ insert p (insertAtPath ps value JsonNull) o
  Just i -> JsonArray $ replicate (i :: Int) JsonNull ++ [insertAtPath ps value JsonNull]
-- Insert value into an array
insertAtPath (p : ps) value (JsonArray js) = case readMaybe p of
  Nothing -> JsonObject [(p, insertAtPath ps value JsonNull)]
  -- Insert it into array if it already has the length
  Just i
    | i < length js ->
      let (xs, ys) = splitAt i js
       in -- Replacing the index with the new item, for this we need to drop 1 element at the end
          JsonArray $ xs ++ [insertAtPath ps value JsonNull] ++ drop 1 ys
    -- Otherwise append items to the list and put it at the end
    | otherwise -> JsonArray $ js ++ replicate ((i :: Int) - length js) JsonNull ++ [insertAtPath ps value JsonNull]
-- It is inserting along a new path, denoted with JsonNull
insertAtPath (p : ps) value JsonNull = case readMaybe p of
  Nothing -> JsonObject [(p, insertAtPath ps value JsonNull)]
  Just i -> JsonArray $ replicate (i :: Int) JsonNull ++ [insertAtPath ps value JsonNull]
-- Data is always an array or an object in the top layer, everything else is wrong
insertAtPath _ _ _ = error "Error invalid Json, your json data is not an array or object"

-- | Generate random Json given a size
genSizedRandomJson :: Size -> Gen Json
genSizedRandomJson s@(Size size)
  -- If size less or equal to 0 a final item is closed
  | size <= 0 =
    choice
      [ return JsonNull,
        fst <$> genGenericJsonBool,
        fst <$> genGenericJsonNumber,
        fst <$> genGenericJsonString
      ]
  -- If size is greater than 0 we expand with an array or object
  | otherwise =
    choice
      [ genSizedRandomJsonArray s,
        JsonObject <$> genSizedRandomJsonObject s
      ]

-- | Generate a Random sized Json array
genSizedRandomJsonArray :: Size -> Gen Json
genSizedRandomJsonArray size = do
  sizes <- genUnbalancedSizeList size
  JsonArray <$> mapM genSizedRandomJson sizes

-- | Generate a Random size Json array that does not contain any objects
genSizedNestedJsonArray :: Size -> Gen Json
genSizedNestedJsonArray size
  | size <= 0 =
    choice
      [ return JsonNull,
        fst <$> genGenericJsonBool,
        fst <$> genGenericJsonNumber,
        fst <$> genGenericJsonString
      ]
  | otherwise = do
    sizes <- genUnbalancedSizeList size
    JsonArray <$> mapM genSizedNestedJsonArray sizes

-- | Generate sized Jsonobject entry (pair<key,value>)
genSizedRandomJsonEntry :: Size -> Gen (String, Json)
genSizedRandomJsonEntry size = do
  str <- snd <$> genGenericNonEmptyJsonString
  json <- genSizedRandomJson size
  return (str, json)

-- | Generate random Json object with a given size
genSizedRandomJsonObject :: Size -> Gen JsonObject
genSizedRandomJsonObject size = do
  sizes <- genUnbalancedSizeList size
  M.fromList <$> mapM genSizedRandomJsonEntry sizes

-- | Generate an array of given size that generates a range array
genSizedJsonNumberArray :: Size -> Gen (Json, [Double])
genSizedJsonNumberArray (Size size) = do
  let arr = [1 .. (1.0 + fromIntegral size)] :: [Double]
  return (JsonArray $ map JsonNumber arr, arr)

-- | Generate a flat array of a given size
genSizedFlatArray :: Size -> Gen Json
genSizedFlatArray (Size size) = JsonArray <$> mapM (\_ -> genSizedRandomJson $ Size 0) [0 .. size]