BodyParser w Play Framework. Walidacja danych przychodzących.

W poprzednich wpisach pokazałem w jaki sposób stworzyć projekt w Frameworku Play, oraz pokazałem jak ogólnie walidować obiekty w Javie.
Czas teraz zwalidować JSON’a w Play!

 

Wymagania

 

Konfiguracja

Utworzymy pakiet json i w nim prostą klasę:

Nic w niej strasznego nie ma.
Imię, nazwisko, data urodzenia oraz email.

Chcielibyśmy, aby data urodzenia była w przeszłości, i żeby email spełniał podstawowe reguły walidacyjne.
Dodajmy więc odpowiednie adnotacje:

Dwie proste adnotacje: @Past oraz @Email.

To teraz napiszmy jakiś kontroler, który nam pobierze tego jsona z żądania:

Do pliku routes dodajmy wpis:

I wyślijmy takiego jsona na adres: http://localhost:9000/person

Ups… nie waliduje się nic. To źle. Wg opisu adnotacji, powinniśmy otrzymać „jakiś” błąd z powodu tego, że podaliśmy datę w przyszłości i email abc ma się nijak do definicji emaila.

Musimy jakoś uruchomić walidację i przy okazji uprościć sobie trochę sam mechanizm wyciągania jsona z żądania.

Uruchomienie walidacji

Utworzymy sobie coś, co nazywa się BodyParser. BodyParser, to twór, za pomocą którego zamienimy przychodzące body na obiekt. Jeszcze zanim żądanie dojdzie do metody kontrolera. Dzięki temu podejściu, w kontrolerze będziemy mogli skupić się na „mięsku”, a samą konwersję json -> obiekt oraz jego walidację  mieć gdzieś obok.

Zależności walidatora

Dodajemy do build.sbt następujące wpisy:

Zauważcie, że nie ma wpisu dotyczącego właściwej implementacji walidatorów. Play ma je już w sobie, więc nie musimy nic dodawać.

BodyParser

Czas na mięsko 🙂 Napiszmy taki BodyParser:

Kod wydaje się dość skomplikowany. Ale po kolei…

BodyParser musi implementować interfejs BodyParser<> od typu, jaki chcemy uzyskać. Chcemy uzyskać PersonJson, więc go tutaj definiuję.

Implementacja tego interfejsu będzie wymagała JsonParsera (no bo konwertujemy przecież Jsona), oraz elementu, który się zwie Executor – to jest w najprostszych słowach wykonawca wątku. Jego opis trochę wybiega poza temat tego wpisu.

Wstrzykujemy sobie json parsera oraz executora:

Teraz najtrudniejsze :

Akumulator

CO TO JEST ? Wygląda groźnie 🙂

Klasa Accumulator jest częścią Akka Streams, która jest sama w sobie czystą poezją, niekoniecznie prostą. Kiedyś o niej napiszę, a póki co odsyłam na http://doc.akka.io/docs/akka/2.5.3/java/stream/index.html

A po polsku, Accumulator jest to funkcja, która przyjmuje ByteString (który jest częścią streamu), a zwraca obiekt typu F.Either<Result,PersonJson>.

Klasa F.Either (albo Either, bo F, to klasa Play, która zawiera w sobie klasy Either, Tuple, i inne twory funkcyjne, których brakuje w Javie) to klasa, która może mieć wartość lewą (klasa Left) lub prawą (klasa Right). Przyjmuje się, że lewa jest zła*, prawa jest dobra**. Według opisu: F.Either<Result,PersonJson> widzimy, że lewa to Result, a prawa to PersonJson.

* Zła czyli nieodpowiednia, błędna, „lewa”.
** Dobra – prawa, z angielska – right – that’s right, you’re right.

Czyli teraz potrafimy to wszystko przeczytać i zrozumieć.

Musimy zwrócić funkcję, która jako wejście przyjmuje ByteString, a jako rezultat ma zwrócić albo Result (czyli odpowiedź http) jako błąd, albo PersonJson jako sukces.

No to mamy:

jsonParser, który sobie wstrzyknęliśmy jest BodyParserem, a więc również implementuje tę funkcję, co więcej zwraca nam ją. Potrzebujemy przekonwertować json’a na PersonJson, a więc używamy jej.

jsonAccumulator jak każdy twór (monad) funkcyjny, udostępnia fukncję map, którą użyjemy…

jsonOrError to jest właśnie wynik funkcji z wywołania parsowania jsona, i zawiera on albo Left (błąd – czyli Result), albo Right (czyli JsonNode, który chcemy przekonwertować).

Cała reszta jest już właściwie prosta.
Jeśli jest Left, to zwracamy go dalej, jeśli Right,to próbujemy utworzyć PersonJson z Jsona:

Jeśli się uda, to uruchamiamy walidację:

I następnie w zależności od powodzenia walidacji zwracamy Left lub Right:

Sam proces może zwrócić nam exception, stąd wszystko opakowane w try/catch.

Do funkcji map akumulatora musimy jeszcze podać Executora, gdyż jest to proces asynchroniczny.

A jak to teraz wszystko razem zrobić, żeby działało ? Musimy tylko dodać naszego BodyParsera do metody kontrolera. Prosta adnotacja i mała modyfikacja kodu:

I mamy to co chcieliśmy. BodyParser, który nam zamienia żądanie na obiekt, oraz pobranie już zwalidowanego obiektu PersonJson do kontrolera.

Możemy oczywiście tego BodyParsera „ugenerycznić, aby nie robić za każdym razem tego samego. Trochę zmienimy klasę BodyParsera:

Mamy go abstrakcyjnego, który jako parametr przyjmie klasę docelową.

Do niego tworzymy klasę główną:

I używamy jej w kontrolerze:

I to tyle.

Nie było prosto, ale się udało. Mamy generyczny walidator oraz konwerter Jsona na obiekt docelowy wydzielony poza kontroler.

Oczywiście nie jest to jedyne podejście, przy odrobinie chęci możemy walidator wystawić jako osobny BodyParser, albo jako tzw. ActionComposition, ale postaram się to pokazać przy innej okazji.

 

 

«
»