1
/*
2
 * This file is part of the diffson project.
3
 * Copyright (c) 2019 Lucas Satabin
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package diffson
18
package playJson
19

20
import jsonpatch._
21
import jsonpointer._
22
import jsonmergepatch._
23

24
import cats._
25
import cats.implicits._
26

27
import play.api.libs.json._
28

29
import scala.annotation.tailrec
30

31
object DiffsonProtocol {
32

33
  private def errorToException(error: JsError): Exception =
34 0
    JsError.toFlatForm(error) match {
35 0
      case Seq((_, Seq(e, _*)), _*) => new PatchException(e.message)
36 0
      case _                        => new PatchException("Empty json error")
37
    }
38

39
  implicit object JsResultInstances extends MonadError[JsResult, Throwable] {
40
    def pure[A](a: A): JsResult[A] =
41 1
      JsSuccess(a)
42

43
    def handleErrorWith[A](fa: JsResult[A])(f: Throwable => JsResult[A]): JsResult[A] =
44 0
      fa.recoverWith(f.compose(errorToException(_)))
45

46
    def raiseError[A](e: Throwable): JsResult[A] =
47 0
      JsError(e.getMessage)
48

49
    def flatMap[A, B](fa: JsResult[A])(f: A => JsResult[B]): JsResult[B] =
50 1
      fa.flatMap(f)
51

52
    @tailrec
53
    def tailRecM[A, B](a: A)(f: A => JsResult[Either[A, B]]): JsResult[B] =
54 0
      f(a) match {
55
        case e @ JsError(_)         => e
56 0
        case JsSuccess(Left(a), _)  => tailRecM(a)(f)
57 0
        case JsSuccess(Right(b), p) => JsSuccess(b, p)
58
      }
59

60
  }
61

62
  implicit val PointerFormat: Format[Pointer] =
63 1
    Format[Pointer](Reads {
64 0
      case JsString(s) => Pointer.parse[JsResult](s)
65 0
      case value       => JsError(f"Pointer expected: $value")
66 1
    }, Writes(p => JsString(p.show)))
67

68
  implicit val OperationFormat: Format[Operation[JsValue]] =
69 1
    Format[Operation[JsValue]](
70 1
      Reads {
71 1
        case obj @ play.api.libs.json.JsObject(fields) if fields.contains("op") =>
72 1
          fields("op") match {
73
            case JsString("add") =>
74
              (fields.get("path"), fields.get("value")) match {
75
                case (Some(JsString(path)), Some(value)) =>
76 1
                  Pointer.parse[JsResult](path).map(Add(_, value))
77
                case _ =>
78 1
                  JsError("missing 'path' or 'value' field")
79
              }
80
            case JsString("remove") =>
81
              (fields.get("path"), fields.get("old")) match {
82
                case (Some(JsString(path)), old) =>
83 1
                  Pointer.parse[JsResult](path).map(Remove(_, old))
84
                case _ =>
85 0
                  JsError("missing 'path' field")
86
              }
87
            case JsString("replace") =>
88
              (fields.get("path"), fields.get("value"), fields.get("old")) match {
89
                case (Some(JsString(path)), Some(value), old) =>
90 1
                  Pointer.parse[JsResult](path).map(Replace(_, value, old))
91
                case _ =>
92 1
                  JsError("missing 'path' or 'value' field")
93
              }
94
            case JsString("move") =>
95
              (fields.get("from"), fields.get("path")) match {
96
                case (Some(JsString(from)), Some(JsString(path))) =>
97 1
                  (Pointer.parse[JsResult](from), Pointer.parse[JsResult](path)).mapN(Move(_, _))
98
                case _ =>
99 1
                  JsError("missing 'from' or 'path' field")
100
              }
101
            case JsString("copy") =>
102
              (fields.get("from"), fields.get("path")) match {
103
                case (Some(JsString(from)), Some(JsString(path))) =>
104 1
                  (Pointer.parse[JsResult](from), Pointer.parse[JsResult](path)).mapN(Copy(_, _))
105
                case _ =>
106 1
                  JsError("missing 'from' or 'path' field")
107
              }
108
            case JsString("test") =>
109
              (fields.get("path"), fields.get("value")) match {
110
                case (Some(JsString(path)), Some(value)) =>
111 1
                  Pointer.parse[JsResult](path).map(Test(_, value))
112
                case _ =>
113 1
                  JsError("missing 'path' or 'value' field")
114
              }
115
            case op =>
116 1
              JsError(f"Unknown operation ${Json.stringify(op)}")
117
          }
118
        case value =>
119 0
          JsError(f"Operation[JsValue] expected: $value")
120
      },
121 1
      Writes {
122
        case Add(path, value) =>
123 1
          Json.obj("op" -> JsString("add"), "path" -> JsString(path.show), "value" -> value)
124
        case Remove(path, Some(old)) =>
125 0
          Json.obj("op" -> JsString("remove"), "path" -> JsString(path.show), "old" -> old)
126
        case Remove(path, None) =>
127 1
          Json.obj("op" -> JsString("remove"), "path" -> JsString(path.show))
128
        case Replace(path, value, Some(old)) =>
129 0
          Json.obj("op" -> JsString("replace"), "path" -> JsString(path.show), "value" -> value, "old" -> old)
130
        case Replace(path, value, None) =>
131 1
          Json.obj("op" -> JsString("replace"), "path" -> JsString(path.show), "value" -> value)
132
        case Move(from, path) =>
133 1
          Json.obj("op" -> JsString("move"), "from" -> JsString(from.show), "path" -> JsString(path.show))
134
        case Copy(from, path) =>
135 1
          Json.obj("op" -> JsString("copy"), "from" -> JsString(from.show), "path" -> JsString(path.show))
136
        case Test(path, value) =>
137 1
          Json.obj("op" -> JsString("test"), "path" -> JsString(path.show), "value" -> value)
138
      })
139

140
  implicit val JsonPatchFormat: Format[JsonPatch[JsValue]] =
141 1
    Format[JsonPatch[JsValue]](
142 1
      Reads[JsonPatch[JsValue]] { js =>
143 1
        js.validate[List[Operation[JsValue]]].map(JsonPatch(_)).recoverWith {
144 1
          case JsError(errors) => JsError((JsPath -> Seq(JsonValidationError("JsonPatch[JsValue] expected"))) +: errors)
145
        }
146
      },
147 1
      Writes(patch => play.api.libs.json.JsArray(patch.ops.map(Json.toJson(_)).toVector)))
148

149
  implicit val JsonMergePatchFormat: Format[JsonMergePatch[JsValue]] =
150 1
    Format[JsonMergePatch[JsValue]](
151 1
      Reads[JsonMergePatch[JsValue]] {
152 1
        case play.api.libs.json.JsObject(flds) => JsSuccess(JsonMergePatch.Object(flds.toMap))
153 1
        case value                             => JsSuccess(JsonMergePatch.Value(value))
154
      },
155 1
      Writes {
156 1
        case JsonMergePatch.Object(flds) => play.api.libs.json.JsObject(flds)
157
        case JsonMergePatch.Value(v)     => v
158
      })
159

160
}

Read our documentation on viewing source code .

Loading