git.iko.soy/typeable/schematic
on 0.2.0.0 help sign in
136 lines 4.8 kB history raw
1# schematic
3[![Build Status](https://travis-ci.org/typeable/schematic.svg?branch=master)](https://travis-ci.org/typeable/schematic)
5## Goal
7The goal of the library is to provide a type-safe transport layer for serializing and validating JSON. It can be thought of as a subset of [json-schema](http://json-schema.org), which is basically a specification of a JSON document. The other goal is getting as much as possible from this specification for free. Right now the following bits are prototyped:
9* All of the instantiations of the transport types structurally follow the schema provided by user
10* Serializers are generic, so they follow from the type-level schema and the're supposed to have a roundtrip property by an implementation.
11* Runtime value validators are generated from the schema. Validation errors are reported as a pairs of a json-path to the element and an error message.
12* There are migrations. It's possible to describe a series of migrations to the schema and have all the necessary machinery to deserialize a user specified version of the schema if there are multiple versions available.
13* Schematic schemas can be exported to [json-schema](http://json-schema.org)
15Be aware that library is experimental and subject to change a lot. The current state can be viewed as a prototype.
17## Installation
19* Install [Stack](https://github.com/commercialhaskell/stack)
21```
22$ stack install schematic
23```
25## Basic Examples
27```
28type SchemaExample
29 = SchemaObject
30 '[ '("foo", SchemaArray '[AEq 1] (SchemaNumber '[NGt 10]))
31 , '("bar", SchemaOptional (SchemaText '[TEnum '["foo", "bar"]]))]
32```
34* This schema says that's structurally it's a JSON object which has two fields:
35 * field "foo" contains an array of numeric values
36 * field "bar" contains an optional text value
37* It also allows you to validate JSON values itself:
38 * an array in "foo" contains exactly one element
39 * that element must be strictly greater than 10
40 * value of "bar" field must be either "foo" or "bar" if presented
43This one is valid for example
45```
46schemaJson :: ByteString
47schemaJson = "{\"foo\": [13], \"bar\": null}"
48```
50Also valid
52```
53schemaJson :: ByteString
54schemaJson = "{\"foo\": [13], \"bar\": \"bar\"}"
55```
57it can be parsed and validated like this:
59```
60decodeAndValidateJson schemaJson :: ParseResult (JsonRepr SchemaExample)
61```
63`ParseResult` type encodes three possible situations:
64* json is structurally consistent with a schema and runtime validations pass
65* json source is malformed or doesn't correspond to a schema structurally
66* some of the runtime validations have failed, but it's structurally correct
67 overall
69It implies a transport layer representation of that data that can be traversed and transformed to whatever internal types user has. It's called `JsonRepr`. Type parameter is the schema itself.
72```
73jsonExample :: JsonRepr SchemaExample
74jsonExample = ReprObject $
75 FieldRepr (ReprArray [ReprNumber 12])
76 :& FieldRepr (ReprOptional (Just (ReprText "bar")))
77 :& RNil
78```
80## Migrations
82It's possible to represent schema changes as a series of migrations, which describes a series of json-path/change pairs. Migrations can be applied in succession.
84This piece of code will apply a migration to the schema, decode and validate the latest version.
86```
87type SchemaExample
88 = SchemaObject
89 '[ '("foo", SchemaArray '[AEq 1] (SchemaNumber '[NGt 10]))
90 , '("bar", SchemaOptional (SchemaText '[TEnum '["foo", "bar"]]))]
92type TestMigration =
93 'Migration "test_revision"
94 '[ 'Diff '[ 'PKey "bar" ] ('Update ('SchemaText '[]))
95 , 'Diff '[ 'PKey "foo" ] ('Update ('SchemaNumber '[])) ]
97type VS = 'Versioned SchemaExample '[ TestMigration ]
99schemaJsonTopVersion :: ByteString
100schemaJsonTopVersion = "{ \"foo\": 42, \"bar\": \"bar\" }"
101```
103It's possible to decode the latest version like this:
105```
106decodeAndValidateJson schemaJsonTopVersion :: ParseResult (JsonRepr (TopVersion (AllVersions VS)))
107```
109## Lens-compatibility
111It's possible to use `flens` to construct a field lens for a typed object in schematic. Let's suppose you have a schema like this:
113```
114type ArraySchema = 'SchemaArray '[AEq 1] ('SchemaNumber '[NGt 10])
116type ArrayField = '("foo", ArraySchema)
118type FieldsSchema =
119 '[ ArrayField, '("bar", 'SchemaOptional ('SchemaText '[TEnum '["foo", "bar"]]))]
121type SchemaExample = 'SchemaObject FieldsSchema
123```
125There are two ways of working with named fields in the objects:
127* Using the `fget` and `fset` functions: `fget fooProxy (fput newFooVal objectData) == newFooVal`
128* Using the lens library: `set (flens (Proxy @"foo")) newFooVal objectData ^. flens (Proxy @"foo") == newFooVal`
130## Export to json-schema (draft 4)
133## TODO
135* Overall user friendliness
136* Versioned http endpoints