packages feed

hydra-0.1.1: src/main/haskell/Hydra/Ext/Avro/SchemaJson.hs

module Hydra.Ext.Avro.SchemaJson where

import Hydra.Kernel
import Hydra.Impl.Haskell.Dsl.Standard
import Hydra.Impl.Haskell.Ext.Json.Serde
import qualified Hydra.Ext.Avro.Schema as Avro
import qualified Hydra.Ext.Json.Model as Json
import Hydra.Ext.Json.Eliminate

import qualified Control.Monad as CM
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Maybe as Y


avro_aliases = "aliases"
avro_array = "array"
avro_ascending = "ascending"
avro_boolean = "boolean"
avro_bytes = "bytes"
avro_default = "default"
avro_descending = "descending"
avro_doc = "doc"
avro_double = "double"
avro_enum = "enum"
avro_fields = "fields"
avro_fixed = "fixed"
avro_float = "float"
avro_ignore = "ignore"
avro_int = "int"
avro_items = "items"
avro_long = "long"
avro_map = "map"
avro_name = "name"
avro_namespace = "namespace"
avro_null = "null"
avro_order = "order"
avro_record = "record"
avro_size = "size"
avro_string = "string"
avro_symbols = "symbols"
avro_type = "type"
avro_values = "values"

avroSchemaJsonCoder :: Coder s s Avro.Schema Json.Value
avroSchemaJsonCoder = Coder {
  coderEncode = \schema -> fail "not implemented",
  coderDecode = decodeNamedSchema}

avroSchemaStringCoder :: Coder s s Avro.Schema String
avroSchemaStringCoder = Coder {
  coderEncode = \schema -> valueToString <$> coderEncode avroSchemaJsonCoder schema,
  coderDecode = \s -> do
    json <- case stringToValue s of
      Left msg -> fail $ "failed to parse JSON: " ++ msg
      Right j -> pure j
    coderDecode avroSchemaJsonCoder json}

decodeAliases :: M.Map String Json.Value -> Flow s (Maybe [String])
decodeAliases m = do
  aliasesJson <- optArray avro_aliases m
  case aliasesJson of
    Nothing -> pure Nothing
    Just a -> Just <$> CM.mapM expectString a

decodeEnum :: M.Map String Json.Value -> Flow s Avro.NamedType
decodeEnum m = do
  symbolsJson <- requireArray avro_symbols m
  symbols <- CM.mapM expectString symbolsJson
  dflt <- optString avro_default m
  return $ Avro.NamedTypeEnum $ Avro.Enum_ symbols dflt

decodeField :: M.Map String Json.Value -> Flow s Avro.Field
decodeField m = do
  fname <- requireString avro_name m
  doc <- optString avro_doc m
  typ <- require avro_type m >>= decodeSchema
  let dflt = opt avro_default m
  order <- case opt avro_order m of
    Nothing -> pure Nothing
    Just o -> Just <$> (expectString o >>= decodeOrder)
  aliases <- decodeAliases m
  let anns = getAnnotations m
  return $ Avro.Field fname doc typ dflt order aliases anns

decodeFixed :: M.Map String Json.Value -> Flow s Avro.NamedType
decodeFixed m = do
    size <- doubleToInt <$> requireNumber avro_size m
    return $ Avro.NamedTypeFixed $ Avro.Fixed size
  where
    doubleToInt d = if d < 0 then ceiling d else floor d

decodeNamedSchema :: Json.Value -> Flow s Avro.Schema
decodeNamedSchema value = do
  m <- expectObject value
  name <- requireString avro_name m
  ns <- optString avro_namespace m
  typ <- requireString avro_type m
  nt <- case M.lookup typ decoders of
    Nothing -> unexpected "Avro type" typ
    Just d -> d m
  aliases <- decodeAliases m
  doc <- optString avro_doc m
  let anns = getAnnotations m
  return $ Avro.SchemaNamed $ Avro.Named name ns aliases doc nt anns
  where
    decoders = M.fromList [
      (avro_enum, decodeEnum),
      (avro_fixed, decodeFixed),
      (avro_record, decodeRecord)]

decodeOrder :: String -> Flow s Avro.Order
decodeOrder o = case M.lookup o orderMap of
    Nothing -> unexpected "ordering" o
    Just order -> pure order
  where
    orderMap = M.fromList [
      (avro_ascending, Avro.OrderAscending),
      (avro_descending, Avro.OrderDescending),
      (avro_ignore, Avro.OrderIgnore)]

decodeRecord :: M.Map String Json.Value -> Flow s Avro.NamedType
decodeRecord m = do
  fields <- requireArray avro_fields m >>= CM.mapM expectObject >>= CM.mapM decodeField
  return $ Avro.NamedTypeRecord $ Avro.Record fields

decodeSchema :: Json.Value -> Flow s Avro.Schema
decodeSchema v = case v of
  Json.ValueArray els -> Avro.SchemaUnion <$> (Avro.Union <$> (CM.mapM decodeSchema els))
  Json.ValueObject m -> do
      typ <- requireString avro_type m
      case M.lookup typ decoders of
        Nothing -> unexpected "\"array\" or \"map\"" typ
        Just d -> d m
    where
      decoders = M.fromList [
        (avro_array, \m -> do
          items <- require avro_items m >>= decodeSchema
          return $ Avro.SchemaArray $ Avro.Array items),
        (avro_enum, \m -> decodeNamedSchema $ Json.ValueObject m),
        (avro_fixed, \m -> decodeNamedSchema $ Json.ValueObject m),
        (avro_map, \m -> do
          values <- require avro_values m >>= decodeSchema
          return $ Avro.SchemaMap $ Avro.Map_ values),
        (avro_record, \m -> decodeNamedSchema $ Json.ValueObject m)]
  Json.ValueString s -> pure $ case M.lookup s schemas of
      Just prim -> Avro.SchemaPrimitive prim
      Nothing -> Avro.SchemaReference s
    where
      schemas = M.fromList [
        (avro_boolean, Avro.PrimitiveBoolean),
        (avro_bytes, Avro.PrimitiveBytes),
        (avro_double, Avro.PrimitiveDouble),
        (avro_float, Avro.PrimitiveFloat),
        (avro_int, Avro.PrimitiveInt),
        (avro_long, Avro.PrimitiveLong),
        (avro_null, Avro.PrimitiveNull),
        (avro_string, Avro.PrimitiveString)]
  Json.ValueNull -> pure $ Avro.SchemaPrimitive $ Avro.PrimitiveNull
  _ -> unexpected "JSON array, object, or string" v

getAnnotations :: M.Map String Json.Value -> M.Map String Json.Value
getAnnotations = M.fromList . L.filter isAnnotation . M.toList
  where
    isAnnotation (k, _) = L.take 1 k == "@"