diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2020 Nikita Volkov
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/library/YamlUnscrambler.hs b/library/YamlUnscrambler.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler.hs
@@ -0,0 +1,610 @@
+module YamlUnscrambler
+(
+  -- * Execution
+  parseText,
+  parseByteString,
+  getExpectations,
+  -- * DSL
+  -- ** Value
+  Value,
+  value,
+  nullableValue,
+  -- *** Helpers
+  sequenceValue,
+  mappingValue,
+  scalarsValue,
+  -- ** Scalar
+  Scalar,
+  stringScalar,
+  nullScalar,
+  boolScalar,
+  scientificScalar,
+  doubleScalar,
+  rationalScalar,
+  boundedIntegerScalar,
+  unboundedIntegerScalar,
+  timestampScalar,
+  dayScalar,
+  timeScalar,
+  uuidScalar,
+  binaryScalar,
+  -- ** Mapping
+  Mapping,
+  foldMapping,
+  byKeyMapping,
+  -- ** Sequence
+  Sequence,
+  foldSequence,
+  byOrderSequence,
+  byKeySequence,
+  -- ** String
+  String,
+  textString,
+  enumString,
+  formattedString,
+  attoparsedString,
+  -- ** ByKey
+  ByKey,
+  atByKey,
+  atOneOfByKey,
+  -- ** ByOrder
+  ByOrder,
+  fetchByOrder,
+  -- * Value types
+  MaxInputSize(..),
+  Signed(..),
+  NumeralSystem(..),
+  CaseSensitive(..),
+)
+where
+
+import YamlUnscrambler.Prelude hiding (String)
+import YamlUnscrambler.Model
+import qualified YamlUnscrambler.Err as Err
+import qualified Attoparsec.Time.ByteString as AsciiAtto
+import qualified Control.Foldl as Fold
+import qualified Data.Attoparsec.ByteString.Char8 as AsciiAtto
+import qualified Data.Attoparsec.Text as TextAtto
+import qualified Data.ByteString as ByteString
+import qualified Data.ByteString.Base64 as Base64
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.List as List
+import qualified Data.Map.Strict as Map
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.UUID as UUID
+import qualified Data.Vector as Vector
+import qualified Data.Vector.Generic as GenericVector
+import qualified Data.Yaml.Parser as Yaml
+import qualified Text.Libyaml as Libyaml
+import qualified YamlUnscrambler.AsciiAtto as AsciiAtto
+import qualified YamlUnscrambler.CompactErrRendering as CompactErrRendering
+import qualified YamlUnscrambler.Expectations as Ex
+import qualified YamlUnscrambler.Util.ByteString as ByteString
+import qualified YamlUnscrambler.Util.HashMap as HashMap
+import qualified YamlUnscrambler.Util.Text as Text
+import qualified YamlUnscrambler.Util.Vector as Vector
+import qualified YamlUnscrambler.Util.Yaml as Yaml
+
+
+-- * Execution
+-------------------------
+
+parseText :: Value a -> Text -> Either Text a
+parseText value =
+  parseByteString value . Text.encodeUtf8
+
+parseByteString :: Value a -> ByteString -> Either Text a
+parseByteString (Value {..}) input =
+  do
+    Yaml.RawDoc value map <- Yaml.parseByteStringToRawDoc input
+    valueParser value map & first CompactErrRendering.renderErrAtPath
+
+{-|
+Get a tree of expectations, which can then be converted into
+documentation for people working with the YAML document or
+into one of the spec formats (e.g., YAML Spec, JSON Spec).
+-}
+getExpectations :: Value a -> Ex.Value
+getExpectations =
+  valueExpectation
+
+
+-- *
+-------------------------
+
+data Value a =
+  Value {
+    valueExpectation :: Ex.Value,
+    valueParser :: Yaml.YamlValue -> Yaml.AnchorMap -> Either Err.ErrAtPath a
+  }
+  deriving (Functor)
+
+value :: [Scalar a] -> Maybe (Mapping a) -> Maybe (Sequence a) -> Value a
+value scalars mappings sequences =
+  Value expectations parse
+  where
+    expectations =
+      Ex.Value
+        scalarExpectations
+        (fmap mappingExpectation mappings)
+        (fmap sequenceExpectation sequences)
+    scalarExpectations =
+      fmap scalarExpectation scalars
+    parse input anchorMap =
+      case input of
+        Yaml.Scalar bytes tag style _ ->
+          case scalars of
+            [] ->
+              Left (Err.ErrAtPath [] (Err.UnexpectedScalarErr expectations))
+            _ ->
+              runExcept (asum (fmap parse scalars)) &
+              first convErr
+              where
+                parse scalar =
+                  except $ first (Last . Just) $ scalarParser scalar bytes tag style
+                convErr (Last msg) =
+                  Err.ErrAtPath [] (Err.ScalarErr scalarExpectations bytes tag style msg)
+        Yaml.Mapping input _ ->
+          case mappings of
+            Just mapping ->
+              mappingParser mapping input anchorMap
+            Nothing ->
+              Left (Err.ErrAtPath [] (Err.UnexpectedMappingErr expectations))
+        Yaml.Sequence input _ ->
+          case sequences of
+            Just sequence ->
+              sequenceParser sequence input anchorMap
+            Nothing ->
+              Left (Err.ErrAtPath [] (Err.UnexpectedSequenceErr expectations))
+        Yaml.Alias anchorName ->
+          case Map.lookup anchorName anchorMap of
+            Just value ->
+              parse value anchorMap
+            Nothing ->
+              Left (Err.ErrAtPath [] (Err.UnknownAnchorErr (fromString anchorName)))
+
+nullableValue :: [Scalar a] -> Maybe (Mapping a) -> Maybe (Sequence a) -> Value (Maybe a)
+nullableValue scalars mappings sequences =
+  value
+    ((nullScalar Nothing) : fmap (fmap Just) scalars)
+    (fmap (fmap Just) mappings)
+    (fmap (fmap Just) sequences)
+
+-- ** Helpers
+-------------------------
+
+sequenceValue :: Sequence a -> Value a
+sequenceValue sequence =
+  value [] Nothing (Just sequence)
+
+mappingValue :: Mapping a -> Value a
+mappingValue mapping =
+  value [] (Just mapping) Nothing
+
+scalarsValue :: [Scalar a] -> Value a
+scalarsValue scalars =
+  value scalars Nothing Nothing
+
+
+-- *
+-------------------------
+
+data Scalar a =
+  Scalar {
+    scalarExpectation :: Ex.Scalar,
+    scalarParser :: ByteString -> Libyaml.Tag -> Libyaml.Style -> Either Text a
+  }
+  deriving (Functor)
+
+bytesParsingScalar :: Ex.Scalar -> (ByteString -> Either Text a) -> Scalar a
+bytesParsingScalar expectation parser =
+  Scalar expectation (\ bytes _ _ -> parser bytes)
+
+attoparsedScalar :: Ex.Scalar -> AsciiAtto.Parser a -> Scalar a
+attoparsedScalar expectation parser =
+  bytesParsingScalar expectation $
+  first (const "") . AsciiAtto.parseOnly (parser <* AsciiAtto.endOfInput)
+
+sizedScalar :: MaxInputSize -> Scalar a -> Scalar a
+sizedScalar (MaxInputSize maxInputSize) (Scalar {..}) =
+  Scalar scalarExpectation $ \ bytes tag style ->
+    if ByteString.length bytes <= maxInputSize
+      then
+        scalarParser bytes tag style
+      else
+        Left ("Input is longer then the expected maximum of " <> showAsText maxInputSize <> " bytes")
+
+stringScalar :: String a -> Scalar a
+stringScalar (String exp parse) =
+  bytesParsingScalar
+    (Ex.StringScalar exp)
+    (\ bytes -> first showAsText (Text.decodeUtf8' bytes) >>= parse)
+
+nullScalar :: a -> Scalar a
+nullScalar a =
+  Scalar Ex.NullScalar $ \ bytes tag _ ->
+    if
+      tag == Libyaml.NullTag ||
+      ByteString.null bytes ||
+      bytes == "~" ||
+      ByteString.saysNullInCiAscii bytes
+      then
+        Right a
+      else
+        Left "Not null"
+
+boolScalar :: Scalar Bool
+boolScalar =
+  bytesParsingScalar Ex.BoolScalar $ \ bytes ->
+    if ByteString.length bytes <= 5
+      then let
+        lowercased =
+          ByteString.lowercaseInAscii bytes
+        in if elem lowercased ["y", "yes", "on", "true", "t", "1"]
+          then
+            return True
+          else if elem lowercased ["n", "no", "off", "false", "f", "0"]
+            then
+              return False
+            else
+              Left "Not a boolean"
+      else
+        Left "Not a boolean"
+
+scientificScalar :: Scalar Scientific
+scientificScalar =
+  attoparsedScalar Ex.ScientificScalar AsciiAtto.scientific
+
+doubleScalar :: Scalar Double
+doubleScalar =
+  attoparsedScalar Ex.DoubleScalar AsciiAtto.double
+
+rationalScalar :: MaxInputSize -> Scalar Rational
+rationalScalar a =
+  sizedScalar a $
+  attoparsedScalar (Ex.RationalScalar a) AsciiAtto.rational
+
+{-|
+E.g., 'Int', 'Int64', 'Word', but not 'Integer'.
+-}
+boundedIntegerScalar :: (Integral a, FiniteBits a) => Signed -> NumeralSystem -> Scalar a
+boundedIntegerScalar a b =
+  attoparsedScalar (Ex.BoundedIntegerScalar a b) (AsciiAtto.integralScalar a b)
+
+unboundedIntegerScalar :: MaxInputSize -> Signed -> NumeralSystem -> Scalar Integer
+unboundedIntegerScalar a b c =
+  sizedScalar a $
+  attoparsedScalar (Ex.UnboundedIntegerScalar a b c) (AsciiAtto.integralScalar b c)
+
+timestampScalar :: Scalar UTCTime
+timestampScalar =
+  attoparsedScalar Ex.Iso8601TimestampScalar AsciiAtto.utcTimeInISO8601
+
+dayScalar :: Scalar Day
+dayScalar =
+  attoparsedScalar Ex.Iso8601DayScalar AsciiAtto.dayInISO8601
+
+timeScalar :: Scalar TimeOfDay
+timeScalar =
+  attoparsedScalar Ex.Iso8601TimeScalar AsciiAtto.timeOfDayInISO8601
+
+uuidScalar :: Scalar UUID
+uuidScalar =
+  bytesParsingScalar Ex.UuidScalar $ \ bytes ->
+    case UUID.fromASCIIBytes bytes of
+      Just uuid ->
+        return uuid
+      Nothing ->
+        Left "Invalid UUID"
+
+binaryScalar :: Scalar ByteString
+binaryScalar =
+  bytesParsingScalar Ex.Base64BinaryScalar $ \ bytes ->
+    let
+      bytesWithoutNewlines =
+        ByteString.filter (/= 10) bytes
+      in case Base64.decodeBase64 bytesWithoutNewlines of
+        Right res ->
+          return res
+        Left err ->
+          Left err
+
+
+-- *
+-------------------------
+
+data Mapping a =
+  Mapping {
+    mappingExpectation :: Ex.Mapping,
+    mappingParser :: [(Text, Yaml.YamlValue)] -> Yaml.AnchorMap -> Either Err.ErrAtPath a
+  }
+  deriving (Functor)
+
+foldMapping :: (key -> val -> assoc) -> Fold assoc a -> String key -> Value val -> Mapping a
+foldMapping zip (Fold foldStep foldInit foldExtract) key val =
+  Mapping
+    (Ex.MonomorphicMapping (stringExpectation key) (valueExpectation val))
+    parser
+  where
+    parser input anchorMap =
+      foldM step foldInit input &
+      fmap foldExtract
+      where
+        step state (keyInput, valInput) =
+          do
+            parsedKey <- first keyErr (stringParser key keyInput)
+            parsedVal <- first (Err.atSegment keyInput) (valueParser val valInput anchorMap)
+            return $! foldStep state (zip parsedKey parsedVal)
+          where
+            keyErr =
+              Err.ErrAtPath [] .
+              Err.KeyErr (stringExpectation key) keyInput
+
+byKeyMapping :: CaseSensitive -> ByKey Text a -> Mapping a
+byKeyMapping caseSensitive byKey =
+  Mapping expectation parser
+  where
+    expectation =
+      Ex.ByKeyMapping caseSensitive (byKeyExpectation byKey)
+    parser input =
+      either Left (first keysErr) . runExceptT . parser
+      where
+        parser =
+          if coerce caseSensitive
+            then let
+              map =
+                HashMap.fromList input
+              lookup k =
+                HashMap.lookup k map
+              lookupFirst kl =
+                HashMap.lookupFirst kl map
+              in byKeyParser byKey id lookup lookupFirst
+            else let
+              map =
+                HashMap.fromList (fmap (first Text.toLower) input)
+              lookup k =
+                HashMap.lookup (Text.toLower k) map
+              lookupFirst kl =
+                HashMap.lookupFirst (fmap Text.toLower kl) map
+              in byKeyParser byKey id lookup lookupFirst
+        keysErr keys =
+          Err.ErrAtPath [] $
+          Err.NoneOfMappingKeysFoundErr (byKeyExpectation byKey) caseSensitive keysAvail (toList keys)
+          where
+            keysAvail =
+              fmap fst input
+
+
+-- *
+-------------------------
+
+data Sequence a =
+  Sequence {
+    sequenceExpectation :: Ex.Sequence,
+    sequenceParser :: [Yaml.YamlValue] -> Yaml.AnchorMap -> Either Err.ErrAtPath a
+  }
+  deriving (Functor)
+
+foldSequence :: Fold a b -> Value a -> Sequence b
+foldSequence (Fold foldStep foldInit foldExtract) value =
+  Sequence
+    (Ex.MonomorphicSequence (valueExpectation value))
+    parser
+  where
+    parser input anchorMap =
+      foldM step (0 :: Int, foldInit) input &
+      fmap (foldExtract . snd)
+      where
+        step (!index, !state) input =
+          valueParser value input anchorMap &
+          first (Err.atSegment (showAsText index)) &
+          fmap (\ a -> (succ index, foldStep state a))
+
+byOrderSequence :: ByOrder a -> Sequence a
+byOrderSequence (ByOrder {..}) =
+  Sequence
+    (Ex.ByOrderSequence byOrderExpectation)
+    parser
+    where
+      parser input anchorMap =
+        runExceptT (runReaderT (evalStateT byOrderParser (0, input)) anchorMap) &
+        either Left (first mapErr)
+        where
+          mapErr =
+            \ case
+              NotEnoughElementsByOrderErr a ->
+                Err.ErrAtPath [] $
+                Err.NotEnoughElementsErr byOrderExpectation a
+
+byKeySequence :: ByKey Int a -> Sequence a
+byKeySequence (ByKey {..}) =
+  Sequence expectation parser
+  where
+    expectation =
+      Ex.ByKeySequence byKeyExpectation
+    parser input =
+      let
+        vector =
+          Vector.fromList input
+        lookup k =
+          vector Vector.!? k
+        lookupFirst kl =
+          Vector.lookupFirst kl vector
+        in \ anchorMap ->
+          runExceptT (byKeyParser showAsText lookup lookupFirst anchorMap) &
+          either Left (first keysErr)
+      where
+        keysErr keys =
+          Err.ErrAtPath [] $
+          Err.NoneOfSequenceKeysFoundErr byKeyExpectation (toList keys)
+
+
+-- *
+-------------------------
+
+data String a =
+  String {
+    stringExpectation :: Ex.String,
+    stringParser :: Text -> Either Text a
+  }
+  deriving (Functor)
+
+textString :: String Text
+textString =
+  String Ex.AnyString return
+
+enumString :: CaseSensitive -> [(Text, a)] -> String a
+enumString (CaseSensitive caseSensitive) assocList =
+  String expectation parser
+  where
+    expectation =
+      Ex.OneOfString (CaseSensitive caseSensitive) (fmap fst assocList)
+    {-# NOINLINE lookup #-}
+    lookup =
+      if length assocList > 512
+        then if caseSensitive
+          then let
+            hashMap =
+              HashMap.fromList assocList
+            in flip HashMap.lookup hashMap
+          else let
+            hashMap =
+              HashMap.fromList (fmap (first Text.toLower) assocList)
+            in flip HashMap.lookup hashMap . Text.toLower
+        else if caseSensitive
+          then
+            flip List.lookup assocList
+          else
+            flip List.lookup (fmap (first Text.toLower) assocList) . Text.toLower
+    parser text =
+      case lookup text of
+        Just a -> return a
+        _ -> Left "Unexpected value"
+
+formattedString :: Text -> (Text -> Either Text a) -> String a
+formattedString format parser =
+  String
+    (Ex.FormattedString format)
+    parser
+
+attoparsedString :: Text -> TextAtto.Parser a -> String a
+attoparsedString format parser =
+  String
+    (Ex.FormattedString format)
+    (first fromString . TextAtto.parseOnly parser)
+
+
+-- *
+-------------------------
+
+data ByKey key a =
+  ByKey {
+    byKeyExpectation :: Ex.ByKey key,
+    byKeyParser ::
+      (key -> Text) ->
+      (key -> Maybe Yaml.YamlValue) ->
+      ([key] -> Maybe (key, Yaml.YamlValue)) ->
+      Yaml.AnchorMap ->
+      ExceptT (Acc key) (Either Err.ErrAtPath) a
+  }
+  deriving (Functor)
+
+instance Applicative (ByKey key) where
+  pure =
+    ByKey Ex.AnyByKey . const . const . const . const . pure
+  (<*>) (ByKey le lp) (ByKey re rp) =
+    ByKey
+      (Ex.BothByKey le re)
+      (\ a b c d -> lp a b c d <*> rp a b c d)
+
+instance Selective (ByKey key) where
+  select (ByKey le lp) (ByKey re rp) =
+    ByKey
+      (Ex.BothByKey le re)
+      (\ a b c d -> select (lp a b c d) (rp a b c d))
+
+instance Alternative (ByKey key) where
+  empty =
+    ByKey
+      Ex.NoByKey
+      (const (const (const (const empty))))
+  (<|>) (ByKey le lp) (ByKey re rp) =
+    ByKey
+      (Ex.EitherByKey le re)
+      (\ a b c d -> lp a b c d <|> rp a b c d)
+
+atByKey :: key -> Value a -> ByKey key a
+atByKey key valueSpec =
+  ByKey
+    (Ex.LookupByKey [key] (valueExpectation valueSpec))
+    parser
+  where
+    parser renderKey lookup _ env =
+      case lookup key of
+        Just val ->
+          lift $ first (Err.atSegment (renderKey key)) $
+          valueParser valueSpec val env
+        Nothing ->
+          throwE (pure key)
+
+atOneOfByKey :: [key] -> Value a -> ByKey key a
+atOneOfByKey keys valueSpec =
+  ByKey
+    (Ex.LookupByKey keys (valueExpectation valueSpec))
+    parser
+  where
+    parser renderKey _ lookup env =
+      case lookup keys of
+        Just (key, val) ->
+          lift $ first (Err.atSegment (renderKey key)) $
+          valueParser valueSpec val env
+        Nothing ->
+          throwE (fromList keys)
+
+
+-- *
+-------------------------
+
+data ByOrderErr =
+  NotEnoughElementsByOrderErr
+    Int
+
+data ByOrder a =
+  ByOrder {
+    byOrderExpectation :: Ex.ByOrder,
+    byOrderParser :: StateT (Int, [Yaml.YamlValue]) (ReaderT Yaml.AnchorMap (ExceptT ByOrderErr (Either Err.ErrAtPath))) a
+  }
+  deriving (Functor)
+
+instance Applicative ByOrder where
+  pure =
+    ByOrder Ex.AnyByOrder . pure
+  (<*>) (ByOrder le lp) (ByOrder re rp) =
+    ByOrder
+      (Ex.BothByOrder le re)
+      (lp <*> rp)
+
+instance Selective ByOrder where
+  select (ByOrder le lp) (ByOrder re rp) =
+    ByOrder
+      (Ex.BothByOrder le re)
+      (select lp rp)
+
+fetchByOrder :: Value a -> ByOrder a
+fetchByOrder value =
+  ByOrder
+    (Ex.FetchByOrder (valueExpectation value))
+    parser
+  where
+    parser =
+      do
+        (!offset, list) <- get
+        case list of
+          h : t ->
+            do
+              put (succ offset, t)
+              anchorMap <- ask
+              lift $ lift $ lift $ first (Err.atSegment (showAsText offset)) $ valueParser value h anchorMap
+          _ ->
+            throwError $ NotEnoughElementsByOrderErr offset
diff --git a/library/YamlUnscrambler/AsciiAtto.hs b/library/YamlUnscrambler/AsciiAtto.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/AsciiAtto.hs
@@ -0,0 +1,23 @@
+module YamlUnscrambler.AsciiAtto
+where
+
+import YamlUnscrambler.Prelude
+import YamlUnscrambler.Model
+import Data.Attoparsec.ByteString.Char8
+import Attoparsec.Time.ByteString
+
+
+integralScalar :: (Integral a, Bits a) => Signed -> NumeralSystem -> Parser a
+integralScalar (Signed isSigned) numeralSystem =
+  if isSigned
+    then
+      signed numeralParser
+    else
+      numeralParser
+  where
+    numeralParser =
+      case numeralSystem of
+        DecimalNumeralSystem ->
+          decimal
+        HexadecimalNumeralSystem ->
+          hexadecimal
diff --git a/library/YamlUnscrambler/CompactErrRendering.hs b/library/YamlUnscrambler/CompactErrRendering.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/CompactErrRendering.hs
@@ -0,0 +1,128 @@
+module YamlUnscrambler.CompactErrRendering
+(
+  renderErrAtPath,
+)
+where
+
+import YamlUnscrambler.Prelude hiding (intercalate)
+import YamlUnscrambler.Model
+import Text.Builder
+import qualified YamlUnscrambler.Err as Err
+import qualified YamlUnscrambler.Expectations as Ex
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text as Text
+
+
+renderErrAtPath :: Err.ErrAtPath -> Text
+renderErrAtPath =
+  run . errAtPath
+
+path a =
+  "/" <> intercalate "/" (fmap text a)
+
+errAtPath (Err.ErrAtPath a b) =
+  "Error at path " <> path a <> ". " <> reason b
+
+reason =
+  \ case
+    Err.KeyErr a b c ->
+      text c <> ". On input: " <> string (show b) <> ". " <>
+      "Expecting: " <> stringExpectation a
+    Err.NoneOfMappingKeysFoundErr a b c d ->
+      "None of keys found " <> caseSensitively b <> ": " <> string (show d) <> ". " <>
+      "Keys available: " <> string (show c)
+    Err.NoneOfSequenceKeysFoundErr a b ->
+      "None of indices found: " <> string (show b)
+    Err.ScalarErr a b c d e ->
+      foldMap (\ a -> text a <> ". ") (mfilter (not . Text.null) e) <>
+      "Expecting one of the following formats: " <>
+      intercalate ", " (fmap scalarExpectation a) <>
+      foldMap (\ a -> ". Got input: " <> string (show a)) (Text.decodeUtf8' b)
+    Err.UnexpectedScalarErr a ->
+      "Unexpected scalar value"
+    Err.UnexpectedMappingErr a ->
+      "Unexpected mapping value"
+    Err.UnexpectedSequenceErr a ->
+      "Unexpected sequence value"
+    Err.UnknownAnchorErr a ->
+      "Unknown anchor: " <> text a
+    Err.NotEnoughElementsErr a b ->
+      "Not enough elements: " <> decimal b <> ". " <>
+      "Expecting: " <> byOrderExpectation a
+
+scalarExpectation =
+  \ case
+    Ex.StringScalar a ->
+      stringExpectation a
+    Ex.NullScalar ->
+      "null"
+    Ex.BoolScalar ->
+      "boolean"
+    Ex.ScientificScalar ->
+      "scientific"
+    Ex.DoubleScalar ->
+      "double"
+    Ex.RationalScalar a ->
+      "rational of maximum length of " <> maxInputSize a <> " chars"
+    Ex.BoundedIntegerScalar a b ->
+      signed a <> " " <> numeralSystem b
+    Ex.UnboundedIntegerScalar a b c ->
+      signed b <> " " <> numeralSystem c <> " of maximum length of " <> maxInputSize a <> " chars"
+    Ex.Iso8601TimestampScalar ->
+      "timestamp in ISO-8601"
+    Ex.Iso8601DayScalar ->
+      "date in ISO-8601"
+    Ex.Iso8601TimeScalar ->
+      "time in ISO-8601"
+    Ex.UuidScalar ->
+      "UUID"
+    Ex.Base64BinaryScalar ->
+      "binary data in Base-64"
+
+stringExpectation =
+  \ case
+    Ex.AnyString ->
+      "any string"
+    Ex.OneOfString a b ->
+      "one of " <> string (show b) <> "(" <> caseSensitive a <> ")"
+    Ex.FormattedString a ->
+      text a
+
+byOrderExpectation =
+  decimal . count 0
+  where
+    count !a =
+      \ case
+        Ex.AnyByOrder ->
+          a
+        Ex.BothByOrder b c ->
+          countBoth a b c
+        Ex.FetchByOrder _ ->
+          succ a
+    countBoth a b c =
+      case b of
+        Ex.BothByOrder d e ->
+          countBoth a d (Ex.BothByOrder e c)
+        Ex.AnyByOrder ->
+          count a c
+        Ex.FetchByOrder _ ->
+          count (succ a) c
+
+caseSensitive (CaseSensitive a) =
+  "case-" <> bool "insensitive" "sensitive" a
+
+caseSensitively (CaseSensitive a) =
+  "case-" <> bool "insensitively" "sensitively" a
+
+signed (Signed a) =
+  bool "unsigned" "signed" a
+
+numeralSystem =
+  \ case
+    DecimalNumeralSystem ->
+      "decimal"
+    HexadecimalNumeralSystem ->
+      "hexadecimal"
+
+maxInputSize (MaxInputSize a) =
+  decimal a
diff --git a/library/YamlUnscrambler/Err.hs b/library/YamlUnscrambler/Err.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Err.hs
@@ -0,0 +1,78 @@
+module YamlUnscrambler.Err
+(
+  ErrAtPath(..),
+  Err(..),
+  atSegment,
+  -- *
+  MaxInputSize(..),
+  Signed(..),
+  NumeralSystem(..),
+  CaseSensitive(..),
+  -- * Reexports from \"libyaml\"
+  Libyaml.Tag(..),
+  Libyaml.Style(..),
+)
+where
+
+import YamlUnscrambler.Prelude hiding (String)
+import YamlUnscrambler.Model
+import qualified YamlUnscrambler.Expectations as Ex
+import qualified Text.Libyaml as Libyaml
+import qualified Text.Builder as TextBuilder
+
+
+data ErrAtPath =
+  ErrAtPath [Text] Err
+
+data Err =
+  KeyErr
+    Ex.String
+    {-^ Key expectation. -}
+    Text
+    {-^ Key input. -}
+    Text
+    {-^ String parsing error. -}
+    |
+  NoneOfMappingKeysFoundErr
+    (Ex.ByKey Text)
+    CaseSensitive
+    [Text]
+    {-^ Available keys. -}
+    [Text]
+    {-^ Keys looked up. -}
+    |
+  NoneOfSequenceKeysFoundErr
+    (Ex.ByKey Int)
+    [Int]
+    |
+  ScalarErr
+    [Ex.Scalar]
+    {-^ Expected formats. -}
+    ByteString
+    {-^ Input. -}
+    Libyaml.Tag
+    {-^ Tag. -}
+    Libyaml.Style
+    {-^ Style. -}
+    (Maybe Text)
+    {-^ Last error. -}
+    |
+  UnexpectedScalarErr
+    Ex.Value
+    |
+  UnexpectedMappingErr
+    Ex.Value
+    |
+  UnexpectedSequenceErr
+    Ex.Value
+    |
+  UnknownAnchorErr
+    Text
+    |
+  NotEnoughElementsErr
+    Ex.ByOrder
+    Int
+
+atSegment :: Text -> ErrAtPath -> ErrAtPath
+atSegment seg (ErrAtPath path err) =
+  ErrAtPath (seg : path) err
diff --git a/library/YamlUnscrambler/Expectations.hs b/library/YamlUnscrambler/Expectations.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Expectations.hs
@@ -0,0 +1,115 @@
+module YamlUnscrambler.Expectations
+(
+  Value(..),
+  Scalar(..),
+  Mapping(..),
+  Sequence(..),
+  String(..),
+  ByKey(..),
+  ByOrder(..),
+  -- *
+  MaxInputSize(..),
+  Signed(..),
+  NumeralSystem(..),
+  CaseSensitive(..),
+)
+where
+
+import YamlUnscrambler.Prelude hiding (String)
+import YamlUnscrambler.Model
+import qualified YamlUnscrambler.Util.Maybe as Maybe
+
+
+-- *
+-------------------------
+
+data Value =
+  Value
+    [Scalar]
+    (Maybe Mapping)
+    (Maybe Sequence)
+
+data Scalar =
+  StringScalar String
+    |
+  NullScalar
+    |
+  BoolScalar
+    |
+  ScientificScalar
+    |
+  DoubleScalar
+    |
+  RationalScalar MaxInputSize
+    |
+  BoundedIntegerScalar Signed NumeralSystem
+    |
+  UnboundedIntegerScalar MaxInputSize Signed NumeralSystem
+    |
+  Iso8601TimestampScalar
+    |
+  Iso8601DayScalar
+    |
+  Iso8601TimeScalar
+    |
+  UuidScalar
+    |
+  Base64BinaryScalar
+
+data Mapping =
+  MonomorphicMapping String Value
+    |
+  ByKeyMapping CaseSensitive (ByKey Text)
+
+data Sequence =
+  MonomorphicSequence Value
+    |
+  ByOrderSequence ByOrder
+    |
+  ByKeySequence (ByKey Int)
+
+-- *
+-------------------------
+
+data String =
+  {-| Any string as it is. -}
+  AnyString
+    |
+  {-| One of options. Suitable for enumerations. -}
+  OneOfString CaseSensitive [Text] {-^ Options. -}
+    |
+  {-| Must conform to a textually described format. -}
+  FormattedString Text {-^ Description of the format. -}
+
+data ByKey key =
+  AnyByKey
+    |
+  NoByKey
+    |
+  EitherByKey (ByKey key) (ByKey key)
+    |
+  BothByKey (ByKey key) (ByKey key)
+    |
+  LookupByKey [key] {-^ Keys to lookup. -} Value
+
+data ByOrder =
+  AnyByOrder
+    |
+  BothByOrder ByOrder ByOrder
+    |
+  FetchByOrder Value
+
+
+-- *
+-------------------------
+
+instance Semigroup Value where
+  (<>) (Value lScalars lMappings lSequences) (Value rScalars rMappings rSequences) =
+    Value
+      (lScalars <> rScalars)
+      (Maybe.firstNonEmpty lMappings rMappings)
+      (Maybe.firstNonEmpty lSequences rSequences)
+
+instance Monoid Value where
+  mempty =
+    Value [] Nothing Nothing
diff --git a/library/YamlUnscrambler/Model.hs b/library/YamlUnscrambler/Model.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Model.hs
@@ -0,0 +1,24 @@
+module YamlUnscrambler.Model
+where
+
+import YamlUnscrambler.Prelude
+
+
+{-|
+Specification of the maximum allowed length for the input.
+A safety measure to ensure that the parser doesn't exhaust memory
+when parsing to unlimited datatypes.
+-}
+newtype MaxInputSize =
+  MaxInputSize Int
+
+newtype Signed =
+  Signed Bool
+
+data NumeralSystem =
+  DecimalNumeralSystem
+    |
+  HexadecimalNumeralSystem
+
+newtype CaseSensitive =
+  CaseSensitive Bool
diff --git a/library/YamlUnscrambler/Prelude.hs b/library/YamlUnscrambler/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Prelude.hs
@@ -0,0 +1,149 @@
+module YamlUnscrambler.Prelude
+( 
+  module Exports,
+  showAsText,
+)
+where
+
+-- base
+-------------------------
+import Control.Applicative as Exports
+import Control.Arrow as Exports hiding (first, second)
+import Control.Category as Exports
+import Control.Concurrent as Exports
+import Control.Exception as Exports
+import Control.Monad as Exports hiding (fail, mapM_, sequence_, forM_, msum, mapM, sequence, forM)
+import Control.Monad.IO.Class as Exports
+import Control.Monad.Fail as Exports
+import Control.Monad.Fix as Exports hiding (fix)
+import Control.Monad.ST as Exports
+import Data.Bifunctor as Exports
+import Data.Bits as Exports
+import Data.Bool as Exports
+import Data.Char as Exports
+import Data.Coerce as Exports
+import Data.Complex as Exports
+import Data.Data as Exports
+import Data.Dynamic as Exports
+import Data.Either as Exports
+import Data.Fixed as Exports
+import Data.Foldable as Exports hiding (toList)
+import Data.Function as Exports hiding (id, (.))
+import Data.Functor as Exports
+import Data.Functor.Compose as Exports
+import Data.Functor.Contravariant as Exports
+import Data.Int as Exports
+import Data.IORef as Exports
+import Data.Ix as Exports
+import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')
+import Data.List.NonEmpty as Exports (NonEmpty(..))
+import Data.Maybe as Exports
+import Data.Monoid as Exports hiding (Alt)
+import Data.Ord as Exports
+import Data.Proxy as Exports
+import Data.Ratio as Exports
+import Data.STRef as Exports
+import Data.String as Exports
+import Data.Traversable as Exports
+import Data.Tuple as Exports
+import Data.Unique as Exports
+import Data.Version as Exports
+import Data.Void as Exports
+import Data.Word as Exports
+import Debug.Trace as Exports
+import Foreign.ForeignPtr as Exports
+import Foreign.Ptr as Exports
+import Foreign.StablePtr as Exports
+import Foreign.Storable as Exports
+import GHC.Conc as Exports hiding (orElse, withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)
+import GHC.Exts as Exports (IsList(..), lazy, inline, sortWith, groupWith)
+import GHC.Generics as Exports (Generic)
+import GHC.IO.Exception as Exports
+import Numeric as Exports
+import Prelude as Exports hiding (fail, concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))
+import System.Environment as Exports
+import System.Exit as Exports
+import System.IO as Exports (Handle, hClose)
+import System.IO.Error as Exports
+import System.IO.Unsafe as Exports
+import System.Mem as Exports
+import System.Mem.StableName as Exports
+import System.Timeout as Exports
+import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)
+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)
+import Text.Printf as Exports (printf, hPrintf)
+import Text.Read as Exports (Read(..), readMaybe, readEither)
+import Unsafe.Coerce as Exports
+
+-- selective
+-------------------------
+import Control.Selective as Exports
+
+-- text
+-------------------------
+import Data.Text as Exports (Text)
+
+-- bytestring
+-------------------------
+import Data.ByteString as Exports (ByteString)
+
+-- vector
+-------------------------
+import Data.Vector as Exports (Vector)
+
+-- hashable
+-------------------------
+import Data.Hashable as Exports (Hashable)
+
+-- containers
+-------------------------
+import Data.Map.Strict as Exports (Map)
+
+-- unordered-containers
+-------------------------
+import Data.HashSet as Exports (HashSet)
+import Data.HashMap.Strict as Exports (HashMap)
+
+-- uuid
+-------------------------
+import Data.UUID as Exports (UUID)
+
+-- foldl
+-------------------------
+import Control.Foldl as Exports (Fold(..))
+
+-- transformers
+-------------------------
+import Control.Monad.IO.Class as Exports
+import Control.Monad.Trans.Class as Exports
+import Control.Monad.Trans.Cont as Exports hiding (shift, callCC)
+import Control.Monad.Trans.Except as Exports (ExceptT(ExceptT), Except, except, runExcept, runExceptT, mapExcept, mapExceptT, withExcept, withExceptT, throwE, catchE)
+import Control.Monad.Trans.Maybe as Exports
+import Control.Monad.Trans.Reader as Exports (Reader, runReader, mapReader, withReader, ReaderT(ReaderT), runReaderT, mapReaderT, withReaderT)
+import Control.Monad.Trans.State.Strict as Exports (State, runState, evalState, execState, mapState, withState, StateT(StateT), runStateT, evalStateT, execStateT, mapStateT, withStateT)
+import Control.Monad.Trans.Writer.Strict as Exports (Writer, runWriter, execWriter, mapWriter, WriterT(..), execWriterT, mapWriterT)
+
+-- mtl
+-------------------------
+import Control.Monad.Cont.Class as Exports
+import Control.Monad.Error.Class as Exports hiding (Error(..))
+import Control.Monad.Reader.Class as Exports
+import Control.Monad.State.Class as Exports
+import Control.Monad.Writer.Class as Exports
+
+-- scientific
+-------------------------
+import Data.Scientific as Exports (Scientific)
+
+-- time
+-------------------------
+import Data.Time as Exports
+import Data.Time.Clock.System as Exports
+import Data.Time.Clock.POSIX as Exports
+
+-- acc
+-------------------------
+import Acc as Exports (Acc)
+
+showAsText :: Show a => a -> Text
+showAsText = show >>> fromString
diff --git a/library/YamlUnscrambler/Util/ByteString.hs b/library/YamlUnscrambler/Util/ByteString.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/ByteString.hs
@@ -0,0 +1,19 @@
+module YamlUnscrambler.Util.ByteString
+where
+
+import YamlUnscrambler.Prelude hiding (map, length)
+import Data.ByteString
+import qualified YamlUnscrambler.Util.Word8 as Word8
+
+
+lowercaseInAscii =
+  map Word8.lowercaseInAscii
+
+{-# NOINLINE lowercaseNullInAscii #-}
+lowercaseNullInAscii :: ByteString
+lowercaseNullInAscii =
+  "null"
+
+saysNullInCiAscii :: ByteString -> Bool
+saysNullInCiAscii a =
+  length a == 4 && lowercaseInAscii a == lowercaseNullInAscii
diff --git a/library/YamlUnscrambler/Util/HashMap.hs b/library/YamlUnscrambler/Util/HashMap.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/HashMap.hs
@@ -0,0 +1,10 @@
+module YamlUnscrambler.Util.HashMap
+where
+
+import YamlUnscrambler.Prelude hiding (lookup)
+import Data.HashMap.Strict
+
+
+lookupFirst :: (Hashable k, Eq k) => [k] -> HashMap k v -> Maybe (k, v)
+lookupFirst keys map =
+  getFirst (foldMap (\ k -> First (fmap (k,) (lookup k map))) keys)
diff --git a/library/YamlUnscrambler/Util/List.hs b/library/YamlUnscrambler/Util/List.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/List.hs
@@ -0,0 +1,11 @@
+module YamlUnscrambler.Util.List
+where
+
+import YamlUnscrambler.Prelude hiding (lookup)
+
+
+firstNonEmpty :: [a] -> [a] -> [a]
+firstNonEmpty a b =
+  case a of
+    [] -> b
+    _ -> a
diff --git a/library/YamlUnscrambler/Util/Maybe.hs b/library/YamlUnscrambler/Util/Maybe.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/Maybe.hs
@@ -0,0 +1,11 @@
+module YamlUnscrambler.Util.Maybe
+where
+
+import YamlUnscrambler.Prelude hiding (lookup)
+
+
+firstNonEmpty :: Maybe a -> Maybe a -> Maybe a
+firstNonEmpty a b =
+  case a of
+    Just a -> Just a
+    _ -> b
diff --git a/library/YamlUnscrambler/Util/Text.hs b/library/YamlUnscrambler/Util/Text.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/Text.hs
@@ -0,0 +1,18 @@
+module YamlUnscrambler.Util.Text
+where
+
+import YamlUnscrambler.Prelude
+import qualified Data.Attoparsec.Text as Atto
+import qualified Attoparsec.Data as Atto
+
+
+deshowIfPossible :: Text -> Text
+deshowIfPossible a =
+  either (const a) id (deshow a)
+
+deshow :: Text -> Either Text Text
+deshow =
+  first fromString . Atto.parseOnly parser
+  where
+    parser =
+      Atto.show <* Atto.endOfInput
diff --git a/library/YamlUnscrambler/Util/Vector.hs b/library/YamlUnscrambler/Util/Vector.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/Vector.hs
@@ -0,0 +1,10 @@
+module YamlUnscrambler.Util.Vector
+where
+
+import YamlUnscrambler.Prelude hiding (lookup)
+import Data.Vector
+
+
+lookupFirst :: [Int] -> Vector a -> Maybe (Int, a)
+lookupFirst keys vector =
+  getFirst (foldMap (\ k -> First (fmap (k,) (vector !? k))) keys)
diff --git a/library/YamlUnscrambler/Util/Word8.hs b/library/YamlUnscrambler/Util/Word8.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/Word8.hs
@@ -0,0 +1,14 @@
+module YamlUnscrambler.Util.Word8
+where
+
+import YamlUnscrambler.Prelude
+
+
+lowercaseInAscii :: Word8 -> Word8
+lowercaseInAscii a =
+  if
+    65 <= a && a <= 90 ||
+    192 <= a && a <= 214 ||
+    216 <= a && a <= 222
+    then a + 32
+    else a
diff --git a/library/YamlUnscrambler/Util/Yaml.hs b/library/YamlUnscrambler/Util/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/library/YamlUnscrambler/Util/Yaml.hs
@@ -0,0 +1,18 @@
+module YamlUnscrambler.Util.Yaml
+where
+
+import YamlUnscrambler.Prelude
+import qualified Conduit
+import qualified Data.Yaml as Yaml
+import qualified Data.Yaml.Parser as YamlParser
+import qualified Data.Text.Encoding as Text
+import qualified Text.Libyaml as Libyaml
+
+
+parseByteStringToRawDoc :: ByteString -> Either Text YamlParser.RawDoc
+parseByteStringToRawDoc input =
+  first showAsText $
+  unsafePerformIO $
+  try @SomeException $
+  Conduit.runConduitRes $
+  Conduit.fuse (Libyaml.decode input) (YamlParser.sinkRawDoc)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,160 @@
+module Main where
+
+import Prelude hiding (assert)
+import GHC.Exts (fromList)
+import Test.QuickCheck.Instances
+import Test.Tasty
+import Test.Tasty.Runners
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+import qualified YamlUnscrambler as U
+import qualified Test.QuickCheck as QuickCheck
+import qualified Control.Foldl as Fold
+import qualified NeatInterpolation as NeatInterpolation
+import qualified Data.Text as Text
+
+
+main =
+  defaultMain $ 
+  testGroup "All tests" [
+    testCase "Should fail on sequence when no sequence is specified" $ let
+      unscrambler =
+        U.value [] (Just mapping) Nothing
+        where
+          mapping =
+            U.foldMapping (,) Fold.list U.textString value
+            where
+              value =
+                U.value [nullScalar, intScalar] Nothing Nothing
+                where
+                  nullScalar =
+                    U.nullScalar Nothing
+                  intScalar =
+                    fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem
+      input =
+        [NeatInterpolation.text|
+          a: 2
+          b: 3
+          c:
+            - 1
+            - 2
+          |]
+      in case U.parseText unscrambler input of
+        Right res ->
+          assertFailure (show res)
+        Left failure ->
+          assertEqual (Text.unpack failure) "Error at path /c. Unexpected sequence value" failure
+    ,
+    testCase "Domain sum-type regression" $ let
+      unscrambler =
+        doc
+        where
+          doc =
+            U.mappingValue $
+            U.byKeyMapping (U.CaseSensitive True) $
+            asum [
+              Just <$> U.atByKey "sums" sum,
+              pure Nothing
+              ]
+          sum =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString sumVariant
+          sumVariant =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString params
+          params =
+            U.value [nullScalar, intScalar] Nothing Nothing
+            where
+              nullScalar =
+                U.nullScalar Nothing
+              intScalar =
+                fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem
+      input =
+        [NeatInterpolation.text|
+          sums:
+            A:
+              a:
+                - Int
+                - Bool
+              b: Char, Double
+          |]
+      in case U.parseText unscrambler input of
+        Right res ->
+          assertFailure (show res)
+        Left failure ->
+          assertEqual "" "Error at path /sums/A/a. Unexpected sequence value" failure
+    ,
+    testCase "Domain sum-type correct" $ let
+      unscrambler =
+        doc
+        where
+          doc =
+            U.mappingValue $
+            U.byKeyMapping (U.CaseSensitive True) $
+            asum [
+              Just <$> U.atByKey "sums" sum,
+              pure Nothing
+              ]
+          sum =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString sumVariant
+          sumVariant =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString params
+          params =
+            U.value [nullScalar, valueScalar] Nothing Nothing
+            where
+              nullScalar =
+                U.nullScalar Nothing
+              valueScalar =
+                fmap Just $ U.stringScalar U.textString
+      input =
+        [NeatInterpolation.text|
+          sums:
+            A:
+              a: Text
+              b: Int
+          |]
+      in case U.parseText unscrambler input of
+        Right res ->
+          assertEqual "" (Just [("A", [("a", Just "Text"), ("b", Just "Int")])]) res
+        Left failure ->
+          assertFailure (Text.unpack failure)
+    ,
+    testCase "Scalar errors are readable" $ let
+      unscrambler =
+        doc
+        where
+          doc =
+            U.mappingValue $
+            U.byKeyMapping (U.CaseSensitive True) $
+            asum [
+              Just <$> U.atByKey "sums" sum,
+              pure Nothing
+              ]
+          sum =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString sumVariant
+          sumVariant =
+            U.mappingValue $
+            U.foldMapping (,) Fold.list U.textString params
+          params =
+            U.value [nullScalar, intScalar] Nothing Nothing
+            where
+              nullScalar =
+                U.nullScalar Nothing
+              intScalar =
+                fmap Just $ U.boundedIntegerScalar @Int (U.Signed True) U.DecimalNumeralSystem
+      input =
+        [NeatInterpolation.text|
+          sums:
+            A:
+              a: Int
+              b: Char
+          |]
+      in case U.parseText unscrambler input of
+        Right res ->
+          assertFailure (show res)
+        Left failure ->
+          assertEqual "" "Error at path /sums/A/a. Expecting one of the following formats: null, signed decimal. Got input: \"Int\"" failure
+    ]
diff --git a/yaml-unscrambler.cabal b/yaml-unscrambler.cabal
new file mode 100644
--- /dev/null
+++ b/yaml-unscrambler.cabal
@@ -0,0 +1,80 @@
+name: yaml-unscrambler
+version: 0.1
+synopsis: Flexible declarative YAML parsing toolkit
+stability: Experimental
+homepage: https://github.com/nikita-volkov/yaml-unscrambler
+bug-reports: https://github.com/nikita-volkov/yaml-unscrambler/issues
+author: Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>
+copyright: (c) 2020 Nikita Volkov
+license: MIT
+license-file: LICENSE
+build-type: Simple
+cabal-version: >=1.10
+
+source-repository head
+  type: git
+  location: git://github.com/nikita-volkov/yaml-unscrambler.git
+
+library
+  hs-source-dirs: library
+  default-extensions: BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, ViewPatterns
+  default-language: Haskell2010
+  exposed-modules:
+    YamlUnscrambler
+    YamlUnscrambler.Expectations
+  other-modules:
+    YamlUnscrambler.AsciiAtto
+    YamlUnscrambler.CompactErrRendering
+    YamlUnscrambler.Err
+    YamlUnscrambler.Model
+    YamlUnscrambler.Prelude
+    YamlUnscrambler.Util.ByteString
+    YamlUnscrambler.Util.Maybe
+    YamlUnscrambler.Util.HashMap
+    YamlUnscrambler.Util.List
+    YamlUnscrambler.Util.Yaml
+    YamlUnscrambler.Util.Word8
+    YamlUnscrambler.Util.Text
+    YamlUnscrambler.Util.Vector
+  build-depends:
+    acc >=0.1.0.2 && <0.2,
+    attoparsec >=0.13 && <0.14,
+    attoparsec-data >=1.0.5 && <1.1,
+    attoparsec-time >=1.0.1.1 && <1.1,
+    base >=4.11 && <5,
+    base64 >=0.4.2.2 && <0.5,
+    bytestring >=0.10 && <0.11,
+    conduit >=1.3.2 && <1.4,
+    containers >=0.6.2 && <0.7,
+    foldl >=1.4 && <2,
+    hashable >=1 && <2,
+    libyaml >=0.1.2 && <0.2,
+    mtl >=2.2 && <3,
+    scientific >=0.3.6.2 && <0.4,
+    selective >=0.4 && <0.5,
+    text >=1 && <2,
+    text-builder >=0.6.6.1 && <0.7,
+    time >=1.9 && <2,
+    transformers >=0.5 && <0.6,
+    unordered-containers >=0.2.10 && <0.3,
+    uuid >=1.3 && <2,
+    vector >=0.12 && <0.13,
+    yaml >=0.11.5 && <0.12
+
+test-suite test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  default-extensions: BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples, ViewPatterns
+  default-language: Haskell2010
+  main-is: Main.hs
+  build-depends:
+    foldl >=1.4.9 && <2,
+    neat-interpolation >=0.5.1.2 && <0.6,
+    QuickCheck >=2.8.1 && <3,
+    quickcheck-instances >=0.3.11 && <0.4,
+    rerebase >=1.9 && <2,
+    tasty >=0.12 && <2,
+    tasty-hunit >=0.9 && <0.11,
+    tasty-quickcheck >=0.9 && <0.11,
+    yaml-unscrambler
