@@ -22,9 +22,35 @@ Be aware that library is experimental and subject to change a lot. The current s
2222$ stack install schematic
2323```
2424
25−## Basic Examples
25+## GHC Extensions
26+
27+To use this library without any hassle, you should add a few GHC extension
28+either to a module or a cabal file:
2629
2730```
31+DataKinds
32+OverloadedLists
33+OverloadedStrings
34+TypeApplications
35+```
36+
37+`Overloaded`-extensions are being used only by `field` combinator,
38+so it you don't use it - feel free to disable it.
39+
40+## GHC Options
41+
42+I recommend using `{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}`
43+pragma in modules which declare schemas and schema migrations, because `schematic`
44+heavily uses promoted types which should be prefixed with "`" signs and they can
45+be dropped with this option, making it less noisy visually.
46+It's entirely optional and totally opinionated, so feel free to ignore this.
47+Through the documentation, the code is written without any explicit ticks for
48+the same reason.
49+Notice that they're still needed for type-level lists and tuples in schematic code.
50+
51+## Basic Examples
52+
53+```haskell
2854type SchemaExample
2955 = SchemaObject
3056 '[ '("foo", SchemaArray '[AEq 1] (SchemaNumber '[NGt 10]))
@@ -42,21 +68,21 @@ type SchemaExample
4268
4369This one is valid for example
4470
45−```
71+```haskell
4672schemaJson :: ByteString
4773schemaJson = "{\"foo\": [13], \"bar\": null}"
4874```
4975
5076Also valid
5177
52−```
78+```haskell
5379schemaJson :: ByteString
5480schemaJson = "{\"foo\": [13], \"bar\": \"bar\"}"
5581```
5682
5783it can be parsed and validated like this:
5884
59−```
85+```haskell
6086decodeAndValidateJson schemaJson :: ParseResult (JsonRepr SchemaExample)
6187```
6288
@@ -66,24 +92,64 @@ decodeAndValidateJson schemaJson :: ParseResult (JsonRepr SchemaExample)
6692* some of the runtime validations have failed, but it's structurally correct
6793 overall
6894
69−It 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.
95+It implies a transport layer representation of that data that can be traversed
96+and transformed to whatever internal types user has.
97+It's called `JsonRepr`. Type parameter is the schema itself.
98+
99+```haskell
100+jsonExample :: JsonRepr SchemaExample
101+jsonExample = withRepr @SchemaExample
102+ $ field @"foo" [12]
103+ :& field @"bar" (Just "bar")
104+ :& RNil
105+```
70106
107+The most basic and sugar-free way of constructing the same object will look like this:
71108
72109```
73−jsonExample :: JsonRepr SchemaExample
74−jsonExample = ReprObject $
110+jsonExample' :: JsonRepr SchemaExample
111+jsonExample' = ReprObject $
75112 FieldRepr (ReprArray [ReprNumber 12])
76113 :& FieldRepr (ReprOptional (Just (ReprText "bar")))
77114 :& RNil
78115```
79116
117+## Lens-compatibility
118+
119+It'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:
120+
121+```haskell
122+type ArraySchema = SchemaArray '[AEq 1] (SchemaNumber '[NGt 10])
123+
124+type ArrayField = '("foo", ArraySchema)
125+
126+type FieldsSchema =
127+ '[ ArrayField, '("bar", SchemaOptional (SchemaText '[TEnum '["foo", "bar"]]))]
128+
129+type SchemaExample = 'SchemaObject FieldsSchema
130+
131+```
132+
133+There are two ways of working with named fields in the objects:
134+
135+* Using the `fget` and `fset` functions: `fget fooProxy (fput newFooVal objectData) == newFooVal`
136+* Using the lens library: `set (flens (Proxy @"foo")) newFooVal objectData ^. flens (Proxy @"foo") == newFooVal`
137+
80138## Migrations
81139
82−It'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.
140+Usually migrations are implicit and hard to deal with, `schematic` deals with it
141+by giving tools to deal with it to a programmer, being as explicit as possible,
142+notifying the developer of a missing transition with a compile-time error.
143+It allows to build versioned HTTP APIs or migrate data from the older versions
144+when reading from the JSON storages.
145+
146+It's possible to represent schema changes as a series of migrations,
147+which describes a series of json-path/change pairs. Migrations can be applied in
148+succession.
83149
84150This piece of code will apply a migration to the schema, decode and validate the latest version.
85151
86−```
152+```haskell
87153type SchemaExample
88154 = SchemaObject
89155 '[ '("foo", SchemaArray '[AEq 1] (SchemaNumber '[NGt 10]))
@@ -91,8 +157,8 @@ type SchemaExample
91157
92158type TestMigration =
93159 'Migration "test_revision"
94− '[ 'Diff '[ 'PKey "bar" ] ('Update ('SchemaText '[]))
95− , 'Diff '[ 'PKey "foo" ] ('Update ('SchemaNumber '[])) ]
160+ '[ Diff '[ PKey "bar" ] (Update (SchemaText '[]))
161+ , Diff '[ PKey "foo" ] (Update (SchemaNumber '[])) ]
96162
97163type VS = 'Versioned SchemaExample '[ TestMigration ]
98164
@@ -106,25 +172,84 @@ It's possible to decode the latest version like this:
106172decodeAndValidateJson schemaJsonTopVersion :: ParseResult (JsonRepr (TopVersion (AllVersions VS)))
107173```
108174
109−## Lens-compatibility
175+It's important to differentiate between schema migrations and data migrations.
176+Schema migration is a change of a schema acceptable by a JSON consumer. It transforms one schema to another.
177+Data migrations on the other hand are functions on the data itself. They transform values
178+acceptable by one schema to values acceptable by another.
179+Let's look at the situation when we have to add a field to a JSON Object of a current schema:
110180
111−It'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:
181+```
182+type SchemaExample = 'SchemaObject
183+ '[ '("foo", 'SchemaArray '[ 'AEq 1] ('SchemaNumber '[ 'NGt 10]))
184+ , '("bar", 'SchemaOptional ('SchemaText '[ 'TEnum '["foo", "bar"]]))]
185+
186+jsonExample :: JsonRepr SchemaExample
187+jsonExample = withRepr @SchemaExample
188+ $ field @"bar" (Just "bar")
189+ :& field @"foo" [12]
190+ :& RNil
191+
192+type AddQuuz =
193+ 'Migration "add_field_quuz"
194+ '[ 'Diff '[] ('AddKey "quuz" (SchemaNumber '[])) ]
195+
196+type DeleteQuuz =
197+ 'Migration "remove_field_quuz"
198+ '[ 'Diff '[] ( 'DeleteKey "quuz") ]
199+
200+type Migrations = '[ AddQuuz
201+ , DeleteQuuz ]
202+
203+type VersionedJson = 'Versioned SchemaExample Migrations
204+
205+migrationList :: MigrationList Identity VersionedJson
206+migrationList
207+ = (migrateObject (\r -> Identity $ field @"quuz" 42 :& r))
208+ :&& shrinkObject
209+ :&& MNil
112210
113211```
114−type ArraySchema = 'SchemaArray '[AEq 1] ('SchemaNumber '[NGt 10])
115212
116−type ArrayField = '("foo", ArraySchema)
213+In this instance `Migrations` is a list of schema migrations and
214+`MigrationList Identity VersionedJson` is a list of data migrations corresponding
215+to the list of schema migrations: `migrateObject (\r -> Identity $ field @"quuz" 42 :& r))`
216+will be used at runtime to transform `JsonRepr SchemaExample` to
217+`JsonRepr (SchemaByRevision "add_quuz" VersionedJson). `Identity` in the type of
218+`MigrationList` is a monad we choose to run our migrations in. If there's need to
219+do database queries or something like that, it can be changed to something more
220+appropriate by a user.
221+`migrateObject` takes a function transforming old fields into new ones, `shrinkObject`
222+allows to shrink the JSON object in case migration only removed fields.
117223
118−type FieldsSchema =
119− '[ ArrayField, '("bar", 'SchemaOptional ('SchemaText '[TEnum '["foo", "bar"]]))]
224+## How do I construct a value of `JsonRepr schema`?
120225
121−type SchemaExample = 'SchemaObject FieldsSchema
226+`JsonRepr schema` is a primary representation of a value serializable/deserializable
227+to JSON, but with a twist. It's guaranteed to correspond a type-level schema,
228+it's a type parameter of a same name.
229+It makes constructing it a little bit more involved, but luckily `schematic`
230+provides a DSL for doing so in a more straightforward fashion. An example would
231+look like this:
122232
233+```haskell
234+type SchemaExample = 'SchemaObject
235+ '[ '("foo", SchemaArray '[ AEq 1] (SchemaNumber '[NGt 10]))
236+ , '("bar", SchemaOptional (SchemaText '[TEnum '["foo", "bar"]]))]
237+
238+jsonExample = withRepr @SchemaExample
239+ $ field @"bar" (Just "bar")
240+ :& field @"foo" [12]
241+ :& RNil
123242```
124243
125−There are two ways of working with named fields in the objects:
244+`@foo` syntax is an explicit type application, which is a feature of GHC 8+, it
245+makes type inference possible without relying on a bunch of `Proxy`s, which
246+makes it syntactically more terse.
247+
248+[GHC Type Applications](https://ghc.haskell.org/trac/ghc/wiki/TypeApplication)
249+
250+`schematic` provides instances for `OverloadedLists` and `OverloadedStrings`
251+GHC extensions to make use of string and list literals for values in a previous
252+example.
126253
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`
129254
130255## Export to json-schema (draft 4)