-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Type-level JSON specification -- -- See the README at: -- https://github.com/owensmurray/json-spec#json-spec @package json-spec @version 1.0.0.0 -- | This module provides a way to specify the shape of your JSON data at -- the type level. -- --
-- data User = User
-- { name :: Text
-- , lastLogin :: UTCTime
-- }
-- deriving stock (Show, Eq)
-- deriving (ToJSON, FromJSON) via (SpecJSON User)
-- instance HasJsonEncodingSpec User where
-- type EncodingSpec User =
-- JsonObject '[
-- Required "name" JsonString,
-- Required "last-login" JsonDateTime
-- ]
-- toJSONStructure user =
-- (Field @"name" (name user),
-- (Field @"last-login" (lastLogin user),
-- ()))
-- instance HasJsonDecodingSpec User where
-- type DecodingSpec User = EncodingSpec User
-- fromJSONStructure
-- (Field @"name" name,
-- (Field @"last-login" lastLogin,
-- ()))
-- =
-- pure User { name , lastLogin }
--
--
-- -- type SpecWithNullableField = -- JsonObject '[ -- Required "nullableProperty" (JsonNullable JsonString) -- ] --JsonNullable :: Specification -> Specification -- | One of two different specifications. Corresponds to json-schema -- "oneOf". Useful for encoding sum types. E.g: -- --
-- data MyType -- = Foo Text -- | Bar Int -- | Baz UTCTime -- instance HasJsonEncodingSpec MyType where -- type EncodingSpec MyType = -- JsonEither -- ( -- JsonObject '[ -- Required "tag" (JsonTag "foo"), -- Required "content" JsonString -- ] -- ) -- ( -- JsonEither -- ( -- JsonObject '[ -- Required "tag" (JsonTag "bar"), -- Required "content" JsonInt -- ] -- ) -- ( -- JsonObject '[ -- Required "tag" (JsonTag "baz"), -- Required "content" JsonDateTime -- ] -- ) -- ) --JsonEither :: Specification -> Specification -> Specification -- | A constant string value JsonTag :: Symbol -> Specification -- | A JSON string formatted as an ISO-8601 string. In Haskell this -- corresponds to UTCTime, and in json-schema it corresponds to -- the "date-time" format. JsonDateTime :: Specification -- | A "let" expression. This is useful for giving names to types, which -- can then be used in the generated code. -- -- This is also useful to shorten repetitive type definitions. For -- example, this repetitive definition: -- --
-- type Triangle = -- JsonObject '[ -- Required "vertex1" (JsonObject '[ -- Required "x" JsonInt, -- Required "y" JsonInt, -- Required "z" JsonInt -- ]), -- Required "vertex2" (JsonObject '[ -- Required "x" JsonInt, -- Required "y" JsonInt, -- Required "z" JsonInt -- ]), -- Required "vertex3" (JsonObject '[ -- Required "x" JsonInt), -- Required "y" JsonInt), -- Required "z" JsonInt) -- ]) -- ] ---- -- Can be written more concisely as: -- --
-- type Triangle =
-- JsonLet
-- '[
-- '("Vertex", JsonObject '[
-- ('x', JsonInt),
-- ('y', JsonInt),
-- ('z', JsonInt)
-- ])
-- ]
-- (JsonObject '[
-- Required "vertex1" JsonRef "Vertex",
-- Required "vertex2" JsonRef "Vertex",
-- Required "vertex3" JsonRef "Vertex"
-- ])
--
--
-- Another use is to define recursive types:
--
--
-- type LabelledTree =
-- JsonLet
-- '[
-- '("LabelledTree", JsonObject '[
-- Required "label", JsonString,
-- Required "children" (JsonArray (JsonRef "LabelledTree"))
-- ])
-- ]
-- (JsonRef "LabelledTree")
--
JsonLet :: [(Symbol, Specification)] -> Specification -> Specification
-- | A reference to a specification which has been defined in a surrounding
-- JsonLet.
JsonRef :: Symbol -> Specification
-- | Some raw, uninterpreted JSON value
JsonRaw :: Specification
-- | Types of this class can be encoded to JSON according to a type-level
-- Specification.
class HasJsonEncodingSpec a where {
-- | The encoding specification.
type EncodingSpec a :: Specification;
}
-- | Encode the value into the structure appropriate for the specification.
toJSONStructure :: HasJsonEncodingSpec a => a -> JSONStructure (EncodingSpec a)
-- | Types of this class can be JSON decoded according to a type-level
-- Specification.
class HasJsonDecodingSpec a where {
-- | The decoding Specification.
type DecodingSpec a :: Specification;
}
-- | Given the structural encoding of the JSON data, parse the structure
-- into the final type. The reason this returns a Parser
-- a instead of just a plain a is because there may still
-- be some invariants of the JSON data that the Specification
-- language is not able to express, and so you may need to fail parsing
-- in those cases. For instance, Specification is not powerful
-- enough to express "this field must contain only prime numbers".
fromJSONStructure :: HasJsonDecodingSpec a => JSONStructure (DecodingSpec a) -> Parser a
-- | Helper for defining ToJSON and FromJSON instances based
-- on HasEncodingJsonSpec.
--
-- Use with -XDerivingVia like:
--
--
-- data MyObj = MyObj
-- { foo :: Int
-- , bar :: Text
-- }
-- deriving (ToJSON, FromJSON) via (SpecJSON MyObj)
-- instance HasEncodingSpec MyObj where ...
-- instance HasDecodingSpec MyObj where ...
--
newtype SpecJSON a
SpecJSON :: a -> SpecJSON a
[unSpecJson] :: SpecJSON a -> a
-- | Structural representation of JsonTag. (I.e. a constant string
-- value.)
data Tag (a :: Symbol)
Tag :: Tag (a :: Symbol)
-- | Structural representation of an object field.
newtype Field (key :: Symbol) t
Field :: t -> Field (key :: Symbol) t
unField :: forall (key :: Symbol) t. Field key t -> t
-- | JSONStructure spec is the Haskell type used to contain
-- the JSON data that will be encoded or decoded according to the
-- provided spec.
--
-- Basically, we represent JSON objects as "list-like" nested tuples of
-- the form:
--
-- -- (Field @key1 valueType, -- (Field @key2 valueType, -- (Field @key3 valueType, -- ()))) ---- -- Note! "Object structures" of this type have the appropriate -- HasField instances, which allows you to use -- -XOverloadedRecordDot to extract values as an alternative to pattern -- matching the whole tuple structure when building your -- HasJsonDecodingSpec instances. See TestHasField in -- the tests for an example -- -- Arrays, booleans, numbers, and strings are just Lists, Bools, -- Scientifics, and Texts respectively. -- -- If the user can convert their normal business logic type to/from this -- tuple type, then they get a JSON encoding to/from their type that is -- guaranteed to be compliant with the Specification type family JSONStructure (spec :: Specification) -- | This is the "Haskell structure" type of JsonRef references. -- -- The main reason why we need this is because of recursion, as explained -- below: -- -- Since the specification is at the type level, and type level haskell -- is strict, specifying a recursive definition the "naive" way would -- cause an infinitely sized type. -- -- For example this won't work: -- --
-- data Foo = Foo [Foo] -- instance HasJsonEncodingSpec Foo where -- type EncodingSpec Foo = JsonArray (EncodingSpec Foo) -- toJSONStructure = ... can't be written ---- -- ... because EncodingSpec Foo would expand strictly into an -- array of EncodingSpec Foo, which would expand strictly... to -- infinity. -- -- Using JsonLet prevents the specification type from being -- infinitely sized, but what about "structure" type which holds real -- values corresponding to the spec? The structure type has to have some -- way to reference itself or else it too would be infinitely sized. -- -- In order to "reference itself" the structure type has to go through a -- newtype somewhere along the way, and that's what this type is for. -- Whenever you use a JsonRef in the spec, the corresponding -- structural type will have a Ref newtype wrapper around the -- "dereferenced" structure type. -- -- For example: -- --
-- data Foo = Foo [Foo]
-- instance HasJsonEncodingSpec Foo where
-- type EncodingSpec Foo =
-- JsonLet
-- '[ '("Foo", JsonArray (JsonRef "Foo")) ]
-- (JsonRef "Foo")
-- toJSONStructure (Foo fs) =
-- Ref [ toJSONStructure <$> fs ]
--
--
-- Strictly speaking, we wouldn't necessarily have to translate
-- every JsonRef into a Ref. In principal we could get away
-- with inserting a Ref somewhere in every mutually recursive
-- cycle. But the type level programming to figure that out a) probably
-- wouldn't do any favors to compilation times, b) is beyond what I'm
-- willing to attempted right now, and c) requires some kind of
-- deterministic and stable choice about where to insert the Ref
-- (which I'm not even certain exists) lest arbitrary
-- HasJsonEncodingSpec or HasJsonDecodingSpec instances
-- break when the members of the recursive cycle change, causing a new
-- choice about where to place the Ref.
newtype Ref (env :: [(Symbol, Specification)]) (spec :: Specification)
Ref :: JStruct env spec -> Ref (env :: [(Symbol, Specification)]) (spec :: Specification)
[unRef] :: Ref (env :: [(Symbol, Specification)]) (spec :: Specification) -> JStruct env spec
-- | Directly decode some JSON accoring to a spec without going through any
-- To/FromJSON instances.
eitherDecode :: forall (spec :: Specification). StructureFromJSON (JSONStructure spec) => Proxy spec -> Value -> Either String (JSONStructure spec)
-- | Given a raw Haskell structure, directly encode it directly into an
-- aeson Value without having to go through any To/FromJSON instances.
--
-- See also: eitherDecode.
encode :: forall (spec :: Specification). StructureToJSON (JSONStructure spec) => Proxy spec -> JSONStructure spec -> Value
-- | Analog of FromJSON, but specialized for decoding our "json
-- representations", and closed to the user because the haskell
-- representation scheme is fixed and not extensible by the user.
--
-- We can't just use FromJSON because the types we are using to
-- represent "json data" (i.e. the JSONStructure type family)
-- already have ToJSON instances. Even if we were to make a
-- bunch of newtypes or whatever to act as the json representation (and
-- therefor also force the user to do a lot of wrapping and unwrapping),
-- that still wouldn't be sufficient because someone could always write
-- an overlapping (or incoherent) ToJSON instance of our
-- newtype! This way we don't have to worry about any of that, and the
-- types that the user must deal with when implementing
-- fromJSONRepr can be simple tuples and such.
class StructureFromJSON a
-- | Specify a field in an object.
data FieldSpec
-- | The field is required
Required :: Symbol -> Specification -> FieldSpec
-- | The field is optionsl
Optional :: Symbol -> Specification -> FieldSpec
-- | Alias for Required.
type (:::) = 'Required
-- | Alias for Optional.
type (::?) = 'Optional
instance (Data.JsonSpec.Decode.StructureFromJSON (Data.JsonSpec.Spec.JSONStructure (Data.JsonSpec.Decode.DecodingSpec a)), Data.JsonSpec.Decode.HasJsonDecodingSpec a) => Data.Aeson.Types.FromJSON.FromJSON (Data.JsonSpec.SpecJSON a)
instance (Data.JsonSpec.Encode.StructureToJSON (Data.JsonSpec.Spec.JSONStructure (Data.JsonSpec.Encode.EncodingSpec a)), Data.JsonSpec.Encode.HasJsonEncodingSpec a) => Data.Aeson.Types.ToJSON.ToJSON (Data.JsonSpec.SpecJSON a)