├── .codebeatignore
├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .travis.yml
├── LICENSE
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── learning
│ │ └── by
│ │ └── example
│ │ └── reactive
│ │ └── microservices
│ │ ├── application
│ │ ├── ApplicationConfig.java
│ │ └── ReactiveMsApplication.java
│ │ ├── exceptions
│ │ ├── GeoLocationNotFoundException.java
│ │ ├── GetGeoLocationException.java
│ │ ├── GetSunriseSunsetException.java
│ │ ├── InvalidParametersException.java
│ │ └── PathNotFoundException.java
│ │ ├── handlers
│ │ ├── ApiHandler.java
│ │ ├── ErrorHandler.java
│ │ └── ThrowableTranslator.java
│ │ ├── model
│ │ ├── ErrorResponse.java
│ │ ├── GeoLocationResponse.java
│ │ ├── GeoTimesResponse.java
│ │ ├── GeographicCoordinates.java
│ │ ├── LocationRequest.java
│ │ ├── LocationResponse.java
│ │ └── SunriseSunset.java
│ │ ├── routers
│ │ ├── ApiRouter.java
│ │ ├── MainRouter.java
│ │ └── StaticRouter.java
│ │ └── services
│ │ ├── GeoLocationService.java
│ │ ├── GeoLocationServiceImpl.java
│ │ ├── SunriseSunsetService.java
│ │ └── SunriseSunsetServiceImpl.java
└── resources
│ ├── application.yaml
│ ├── banner.txt
│ └── public
│ ├── api.yaml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── index.html
│ ├── oauth2-redirect.html
│ ├── swagger-ui-bundle.js
│ ├── swagger-ui-bundle.js.map
│ ├── swagger-ui-standalone-preset.js
│ ├── swagger-ui-standalone-preset.js.map
│ ├── swagger-ui.css
│ ├── swagger-ui.css.map
│ ├── swagger-ui.js
│ └── swagger-ui.js.map
└── test
├── java
└── org
│ └── learning
│ └── by
│ └── example
│ └── reactive
│ └── microservices
│ ├── application
│ ├── ReactiveMsApplicationTest.java
│ └── ReactiveMsApplicationUnitTest.java
│ ├── handlers
│ ├── ApiHandlerTest.java
│ ├── ErrorHandlerTest.java
│ └── ThrowableTranslatorTest.java
│ ├── model
│ └── WrongRequest.java
│ ├── routers
│ ├── ApiRouterTest.java
│ ├── MainRouterTest.java
│ └── StaticRouterTest.java
│ ├── services
│ ├── GeoLocationServiceImplTest.java
│ └── SunriseSunsetServiceImplTest.java
│ └── test
│ ├── BasicIntegrationTest.java
│ ├── HandlersHelper.java
│ ├── RestServiceHelper.java
│ └── tags
│ ├── IntegrationTest.java
│ ├── SystemTest.java
│ └── UnitTest.java
└── resources
├── application-test.yaml
└── json
├── GeoLocationResponse_EMPTY.json
├── GeoLocationResponse_NOT_FOUND.json
├── GeoLocationResponse_OK.json
├── GeoLocationResponse_WRONG_STATUS.json
├── GeoTimesResponse_EMPTY.json
├── GeoTimesResponse_KO.json
└── GeoTimesResponse_OK.json
/.codebeatignore:
--------------------------------------------------------------------------------
1 | src/main/resources/**
2 | src/test/resources/**
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LearningByExample/reactive-ms-example/c40d5855ce2c5e0b6dfb06aa4a421ae9560d6548/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: false
3 | jdk:
4 | - oraclejdk8
5 | before_install:
6 | - chmod +x mvnw
7 | after_success:
8 | - bash <(curl -s https://codecov.io/bash)
9 | branches:
10 | only:
11 | - master
12 | - develop
13 | cache:
14 | directories:
15 | - .autoconf
16 | - $HOME/.m2
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Reactive Micro Services Example
2 | [](/LICENSE)
3 | [](https://travis-ci.org/LearningByExample/reactive-ms-example)
4 | [](https://codecov.io/gh/LearningByExample/reactive-ms-example)
5 | [](https://codebeat.co/projects/github-com-learningbyexample-reactive-ms-example-master)
6 |
7 | ## info
8 | This is an example of doing reactive MicroServices using spring 5 functional web framework and spring boot 2.
9 |
10 | There is a [Kotlin fork](https://github.com/LearningByExample/KotlinReactiveMS) of this service.
11 |
12 | This service provide and API that will get the geo location and the sunrise and sunset times from an address.
13 |
14 | ```Gherkin
15 | Scenario: Get Location
16 | Given I've an address
17 | When I call the location service
18 | Then I should get a geo location
19 | And I should get the sunrise and sunset times
20 | ```
21 | To implement this example we consume a couple of REST APIs.
22 |
23 | This example cover several topics:
24 |
25 | - Functional programing.
26 | - Reactive types.
27 | - Router Functions.
28 | - Static Web-Content.
29 | - Creation on Reactive Java Services/Components.
30 | - Error handling in routes and services.
31 | - Reactive Web Client to consume external REST Services.
32 | - Organizing your project in manageable packaging.
33 |
34 | Includes and in depth look to testing using JUnit5:
35 | - Unit, Integration and System tests.
36 | - Mocking, including reactive functions and JSON responses.
37 | - BDD style assertions.
38 | - Test tags with maven profiles.
39 |
40 | ## usage
41 |
42 | To run this service:
43 |
44 | ```shell
45 | $ mvnw spring-boot:run
46 | ```
47 |
48 | ## Sample requests
49 |
50 | Get from address
51 | ```shell
52 | $ curl -X GET "http://localhost:8080/api/location/Trafalgar%20Square%2C%20London%2C%20England" -H "accept: application/json"
53 | ```
54 |
55 | Post from JSON
56 | ```shell
57 | $ curl -X POST "http://localhost:8080/api/location" -H "accept: application/json" -H "content-type: application/json" -d "{ \"address\": \"Trafalgar Square, London, England\"}"
58 | ```
59 |
60 | Both will produce something like:
61 | ```json
62 | {
63 | "geographicCoordinates": {
64 | "latitude": 51.508039,
65 | "longitude": -0.128069
66 | },
67 | "sunriseSunset": {
68 | "sunrise": "2017-05-21T03:59:08+00:00",
69 | "sunset": "2017-05-21T19:55:11+00:00"
70 | }
71 | }
72 | ```
73 | _All date and times are ISO 8601 UTC without summer time adjustment_
74 | ## API
75 | [ View in the embedded Swagger UI](http://localhost:8080/index.html)
76 |
77 | [ Run in Postman](https://app.getpostman.com/run-collection/498aea143dc572212f17)
78 |
79 | ## Project Structure
80 |
81 | - [main/java](/src/main/java/org/learning/by/example/reactive/microservices)
82 | - [/application](/src/main/java/org/learning/by/example/reactive/microservices/application) : Main Spring boot application and context configuration.
83 | - [/routers](/src/main/java/org/learning/by/example/reactive/microservices/routers) : Reactive routing functions.
84 | - [/handlers](/src/main/java/org/learning/by/example/reactive/microservices/handlers) : Handlers used by the routers.
85 | - [/services](/src/main/java/org/learning/by/example/reactive/microservices/services) : Services for the business logic needed by handlers.
86 | - [/exceptions](/src/main/java/org/learning/by/example/reactive/microservices/exceptions) : Businesses exceptions.
87 | - [/model](/src/main/java/org/learning/by/example/reactive/microservices/model) : POJOs.
88 | - [test/java](/src/test/java/org/learning/by/example/reactive/microservices)
89 | - [/application](/src/test/java/org/learning/by/example/reactive/microservices/application) : Application system and unit tests.
90 | - [/routers](/src/test/java/org/learning/by/example/reactive/microservices/routers) : Integration tests for routes.
91 | - [/handlers](/src/test/java/org/learning/by/example/reactive/microservices/handlers) : Unit tests for handlers.
92 | - [/services](/src/test/java/org/learning/by/example/reactive/microservices/services) : Unit tests for services.
93 | - [/model](/src/test/java/org/learning/by/example/reactive/microservices/model) : POJOs used by the test.
94 | - [/test](/src/test/java/org/learning/by/example/reactive/microservices/test) : Helpers and base classes for testing.
95 |
96 | ## References
97 |
98 | - https://spring.io/blog/2016/09/22/new-in-spring-5-functional-web-framework
99 | - https://spring.io/blog/2017/02/23/spring-framework-5-0-m5-update
100 | - http://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven
101 | - https://github.com/junit-team/junit5-samples
102 | - https://developers.google.com/maps/documentation/geocoding/intro
103 | - https://sunrise-sunset.org/api
104 | - https://en.wikipedia.org/wiki/ISO_8601
105 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | #
58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59 | # for the new JDKs provided by Oracle.
60 | #
61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62 | #
63 | # Apple JDKs
64 | #
65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66 | fi
67 |
68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69 | #
70 | # Apple JDKs
71 | #
72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73 | fi
74 |
75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76 | #
77 | # Oracle JDKs
78 | #
79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80 | fi
81 |
82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83 | #
84 | # Apple JDKs
85 | #
86 | export JAVA_HOME=`/usr/libexec/java_home`
87 | fi
88 | ;;
89 | esac
90 |
91 | if [ -z "$JAVA_HOME" ] ; then
92 | if [ -r /etc/gentoo-release ] ; then
93 | JAVA_HOME=`java-config --jre-home`
94 | fi
95 | fi
96 |
97 | if [ -z "$M2_HOME" ] ; then
98 | ## resolve links - $0 may be a link to maven's home
99 | PRG="$0"
100 |
101 | # need this for relative symlinks
102 | while [ -h "$PRG" ] ; do
103 | ls=`ls -ld "$PRG"`
104 | link=`expr "$ls" : '.*-> \(.*\)$'`
105 | if expr "$link" : '/.*' > /dev/null; then
106 | PRG="$link"
107 | else
108 | PRG="`dirname "$PRG"`/$link"
109 | fi
110 | done
111 |
112 | saveddir=`pwd`
113 |
114 | M2_HOME=`dirname "$PRG"`/..
115 |
116 | # make it fully qualified
117 | M2_HOME=`cd "$M2_HOME" && pwd`
118 |
119 | cd "$saveddir"
120 | # echo Using m2 at $M2_HOME
121 | fi
122 |
123 | # For Cygwin, ensure paths are in UNIX format before anything is touched
124 | if $cygwin ; then
125 | [ -n "$M2_HOME" ] &&
126 | M2_HOME=`cygpath --unix "$M2_HOME"`
127 | [ -n "$JAVA_HOME" ] &&
128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129 | [ -n "$CLASSPATH" ] &&
130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131 | fi
132 |
133 | # For Migwn, ensure paths are in UNIX format before anything is touched
134 | if $mingw ; then
135 | [ -n "$M2_HOME" ] &&
136 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
137 | [ -n "$JAVA_HOME" ] &&
138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139 | # TODO classpath?
140 | fi
141 |
142 | if [ -z "$JAVA_HOME" ]; then
143 | javaExecutable="`which javac`"
144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145 | # readlink(1) is not available as standard on Solaris 10.
146 | readLink=`which readlink`
147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148 | if $darwin ; then
149 | javaHome="`dirname \"$javaExecutable\"`"
150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151 | else
152 | javaExecutable="`readlink -f \"$javaExecutable\"`"
153 | fi
154 | javaHome="`dirname \"$javaExecutable\"`"
155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156 | JAVA_HOME="$javaHome"
157 | export JAVA_HOME
158 | fi
159 | fi
160 | fi
161 |
162 | if [ -z "$JAVACMD" ] ; then
163 | if [ -n "$JAVA_HOME" ] ; then
164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165 | # IBM's JDK on AIX uses strange locations for the executables
166 | JAVACMD="$JAVA_HOME/jre/sh/java"
167 | else
168 | JAVACMD="$JAVA_HOME/bin/java"
169 | fi
170 | else
171 | JAVACMD="`which java`"
172 | fi
173 | fi
174 |
175 | if [ ! -x "$JAVACMD" ] ; then
176 | echo "Error: JAVA_HOME is not defined correctly." >&2
177 | echo " We cannot execute $JAVACMD" >&2
178 | exit 1
179 | fi
180 |
181 | if [ -z "$JAVA_HOME" ] ; then
182 | echo "Warning: JAVA_HOME environment variable is not set."
183 | fi
184 |
185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186 |
187 | # For Cygwin, switch paths to Windows format before running java
188 | if $cygwin; then
189 | [ -n "$M2_HOME" ] &&
190 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
191 | [ -n "$JAVA_HOME" ] &&
192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193 | [ -n "$CLASSPATH" ] &&
194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195 | fi
196 |
197 | # traverses directory structure from process work directory to filesystem root
198 | # first directory with .mvn subdirectory is considered project base directory
199 | find_maven_basedir() {
200 | local basedir=$(pwd)
201 | local wdir=$(pwd)
202 | while [ "$wdir" != '/' ] ; do
203 | if [ -d "$wdir"/.mvn ] ; then
204 | basedir=$wdir
205 | break
206 | fi
207 | wdir=$(cd "$wdir/.."; pwd)
208 | done
209 | echo "${basedir}"
210 | }
211 |
212 | # concatenates all lines of a file
213 | concat_lines() {
214 | if [ -f "$1" ]; then
215 | echo "$(tr -s '\n' ' ' < "$1")"
216 | fi
217 | }
218 |
219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
221 |
222 | # Provide a "standardized" way to retrieve the CLI args that will
223 | # work with both Windows and non-Windows executions.
224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
225 | export MAVEN_CMD_LINE_ARGS
226 |
227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
228 |
229 | exec "$JAVACMD" \
230 | $MAVEN_OPTS \
231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
233 | ${WRAPPER_LAUNCHER} "$@"
234 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | set MAVEN_CMD_LINE_ARGS=%*
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 |
121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123 |
124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
125 | if ERRORLEVEL 1 goto error
126 | goto end
127 |
128 | :error
129 | set ERROR_CODE=1
130 |
131 | :end
132 | @endlocal & set ERROR_CODE=%ERROR_CODE%
133 |
134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
138 | :skipRcPost
139 |
140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
142 |
143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
144 |
145 | exit /B %ERROR_CODE%
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.learning.by.example.reactive.microservices
7 | reactive-ms-example
8 | 1.1.2
9 | jar
10 |
11 | reactive-ms-example
12 | An educational project to learn reactive programming with Spring 5
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.0.0.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 1.8
25 | 5.1.0
26 | 1.1.0
27 | 2.19.1
28 |
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-webflux
34 |
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-starter-test
39 | test
40 |
41 |
42 |
43 | org.jsoup
44 | jsoup
45 | 1.10.2
46 | test
47 |
48 |
49 |
50 | org.mockito
51 | mockito-core
52 | test
53 |
54 |
55 |
56 | org.junit.jupiter
57 | junit-jupiter-api
58 | ${junit.jupiter.version}
59 | test
60 |
61 |
62 |
63 | org.junit.jupiter
64 | junit-jupiter-engine
65 | ${junit.jupiter.version}
66 | test
67 |
68 |
69 |
70 |
71 |
72 |
73 | UnitAndIntegrationTests
74 |
75 | true
76 |
77 |
78 |
79 |
80 | maven-surefire-plugin
81 | ${maven.surefire.plugin.version}
82 |
83 |
84 | UnitTest,IntegrationTest
85 |
86 |
87 |
88 |
89 | org.junit.platform
90 | junit-platform-surefire-provider
91 | ${junit.platform.version}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | IntegrationTests
100 |
101 |
102 |
103 | maven-surefire-plugin
104 | ${maven.surefire.plugin.version}
105 |
106 |
107 | IntegrationTest
108 |
109 |
110 |
111 |
112 | org.junit.platform
113 | junit-platform-surefire-provider
114 | ${junit.platform.version}
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | UnitTests
123 |
124 |
125 |
126 | maven-surefire-plugin
127 | ${maven.surefire.plugin.version}
128 |
129 |
130 | UnitTest
131 |
132 |
133 |
134 |
135 | org.junit.platform
136 | junit-platform-surefire-provider
137 | ${junit.platform.version}
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | SystemTests
146 |
147 |
148 |
149 | maven-surefire-plugin
150 | ${maven.surefire.plugin.version}
151 |
152 |
153 | SystemTest
154 |
155 |
156 |
157 |
158 | org.junit.platform
159 | junit-platform-surefire-provider
160 | ${junit.platform.version}
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | org.springframework.boot
174 | spring-boot-maven-plugin
175 |
176 |
177 | org.jacoco
178 | jacoco-maven-plugin
179 | 0.8.1
180 |
181 |
182 |
183 | prepare-agent
184 |
185 |
186 |
187 | report
188 | test
189 |
190 | report
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | spring-snapshots
201 | Spring Snapshots
202 | https://repo.spring.io/snapshot
203 |
204 | true
205 |
206 |
207 |
208 | spring-milestones
209 | Spring Milestones
210 | https://repo.spring.io/milestone
211 |
212 | false
213 |
214 |
215 |
216 |
217 |
218 |
219 | spring-snapshots
220 | Spring Snapshots
221 | https://repo.spring.io/snapshot
222 |
223 | true
224 |
225 |
226 |
227 | spring-milestones
228 | Spring Milestones
229 | https://repo.spring.io/milestone
230 |
231 | false
232 |
233 |
234 |
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/application/ApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.application;
2 |
3 | import org.learning.by.example.reactive.microservices.handlers.ApiHandler;
4 | import org.learning.by.example.reactive.microservices.handlers.ErrorHandler;
5 | import org.learning.by.example.reactive.microservices.routers.MainRouter;
6 | import org.learning.by.example.reactive.microservices.services.*;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.web.reactive.config.EnableWebFlux;
11 | import org.springframework.web.reactive.function.server.RouterFunction;
12 |
13 | @Configuration
14 | @EnableWebFlux
15 | class ApplicationConfig {
16 |
17 | @Bean
18 | ApiHandler apiHandler(final GeoLocationService geoLocationService, final SunriseSunsetService sunriseSunsetService,
19 | final ErrorHandler errorHandler) {
20 | return new ApiHandler(geoLocationService, sunriseSunsetService, errorHandler);
21 | }
22 |
23 | @Bean
24 | GeoLocationService locationService(@Value("${GeoLocationServiceImpl.endPoint}") final String endPoint) {
25 | return new GeoLocationServiceImpl(endPoint);
26 | }
27 |
28 | @Bean
29 | SunriseSunsetService sunriseSunsetService(@Value("${SunriseSunsetServiceImpl.endPoint}") final String endPoint) {
30 | return new SunriseSunsetServiceImpl(endPoint);
31 | }
32 |
33 | @Bean
34 | ErrorHandler errorHandler() {
35 | return new ErrorHandler();
36 | }
37 |
38 | @Bean
39 | RouterFunction> mainRouterFunction(final ApiHandler apiHandler, final ErrorHandler errorHandler) {
40 | return MainRouter.doRoute(apiHandler, errorHandler);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/application/ReactiveMsApplication.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.application;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class ReactiveMsApplication {
8 | public static void main(String[] args) {
9 | SpringApplication.run(ReactiveMsApplication.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/exceptions/GeoLocationNotFoundException.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.exceptions;
2 |
3 | public class GeoLocationNotFoundException extends Exception{
4 |
5 | public GeoLocationNotFoundException(final String message) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/exceptions/GetGeoLocationException.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.exceptions;
2 |
3 | public class GetGeoLocationException extends Exception {
4 | public GetGeoLocationException(final String message, final Throwable throwable) {
5 | super(message, throwable);
6 | }
7 |
8 | public GetGeoLocationException(final String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/exceptions/GetSunriseSunsetException.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.exceptions;
2 |
3 | public class GetSunriseSunsetException extends Exception {
4 |
5 | public GetSunriseSunsetException(final String message, final Throwable throwable) {
6 | super(message, throwable);
7 | }
8 |
9 | public GetSunriseSunsetException(final String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/exceptions/InvalidParametersException.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.exceptions;
2 |
3 | public class InvalidParametersException extends Exception {
4 |
5 | public InvalidParametersException(final String message) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/exceptions/PathNotFoundException.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.exceptions;
2 |
3 | public class PathNotFoundException extends Exception {
4 |
5 | public PathNotFoundException(final String message) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/handlers/ApiHandler.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.learning.by.example.reactive.microservices.model.GeographicCoordinates;
4 | import org.learning.by.example.reactive.microservices.model.LocationRequest;
5 | import org.learning.by.example.reactive.microservices.model.LocationResponse;
6 | import org.learning.by.example.reactive.microservices.model.SunriseSunset;
7 | import org.learning.by.example.reactive.microservices.services.GeoLocationService;
8 | import org.learning.by.example.reactive.microservices.services.SunriseSunsetService;
9 | import org.springframework.web.reactive.function.server.ServerRequest;
10 | import org.springframework.web.reactive.function.server.ServerResponse;
11 | import reactor.core.publisher.Mono;
12 |
13 | public class ApiHandler {
14 |
15 | private static final String ADDRESS = "address";
16 | private static final String EMPTY_STRING = "";
17 |
18 | private final ErrorHandler errorHandler;
19 |
20 | private final GeoLocationService geoLocationService;
21 | private final SunriseSunsetService sunriseSunsetService;
22 |
23 | public ApiHandler(final GeoLocationService geoLocationService, final SunriseSunsetService sunriseSunsetService,
24 | final ErrorHandler errorHandler) {
25 | this.errorHandler = errorHandler;
26 | this.geoLocationService = geoLocationService;
27 | this.sunriseSunsetService = sunriseSunsetService;
28 | }
29 |
30 | public Mono postLocation(final ServerRequest request) {
31 | return request.bodyToMono(LocationRequest.class)
32 | .map(LocationRequest::getAddress)
33 | .onErrorResume(throwable -> Mono.just(EMPTY_STRING))
34 | .transform(this::buildResponse)
35 | .onErrorResume(errorHandler::throwableError);
36 | }
37 |
38 | public Mono getLocation(final ServerRequest request) {
39 | return Mono.just(request.pathVariable(ADDRESS))
40 | .transform(this::buildResponse)
41 | .onErrorResume(errorHandler::throwableError);
42 | }
43 |
44 | Mono buildResponse(final Mono address) {
45 | return address
46 | .transform(geoLocationService::fromAddress)
47 | .zipWhen(this::sunriseSunset, LocationResponse::new)
48 | .transform(this::serverResponse);
49 | }
50 |
51 | private Mono sunriseSunset(GeographicCoordinates geographicCoordinates) {
52 | return Mono.just(geographicCoordinates).transform(sunriseSunsetService::fromGeographicCoordinates);
53 | }
54 |
55 | Mono serverResponse(Mono locationResponseMono) {
56 | return locationResponseMono.flatMap(locationResponse ->
57 | ServerResponse.ok().body(Mono.just(locationResponse), LocationResponse.class));
58 | }
59 | }
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/handlers/ErrorHandler.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.learning.by.example.reactive.microservices.exceptions.PathNotFoundException;
4 | import org.learning.by.example.reactive.microservices.model.ErrorResponse;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.web.reactive.function.server.ServerRequest;
8 | import org.springframework.web.reactive.function.server.ServerResponse;
9 | import reactor.core.publisher.Mono;
10 |
11 | public class ErrorHandler {
12 |
13 | private static final String NOT_FOUND = "not found";
14 | private static final String ERROR_RAISED = "error raised";
15 | private static final Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
16 |
17 | public Mono notFound(final ServerRequest request) {
18 | return Mono.just(new PathNotFoundException(NOT_FOUND)).transform(this::getResponse);
19 | }
20 |
21 | Mono throwableError(final Throwable error) {
22 | logger.error(ERROR_RAISED, error);
23 | return Mono.just(error).transform(this::getResponse);
24 | }
25 |
26 | Mono getResponse(final Mono monoError) {
27 | return monoError.transform(ThrowableTranslator::translate)
28 | .flatMap(translation -> ServerResponse
29 | .status(translation.getHttpStatus())
30 | .body(Mono.just(new ErrorResponse(translation.getMessage())), ErrorResponse.class));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/handlers/ThrowableTranslator.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.learning.by.example.reactive.microservices.exceptions.GetGeoLocationException;
4 | import org.learning.by.example.reactive.microservices.exceptions.InvalidParametersException;
5 | import org.learning.by.example.reactive.microservices.exceptions.GeoLocationNotFoundException;
6 | import org.learning.by.example.reactive.microservices.exceptions.PathNotFoundException;
7 | import org.springframework.http.HttpStatus;
8 | import reactor.core.publisher.Mono;
9 |
10 | class ThrowableTranslator {
11 |
12 | private final HttpStatus httpStatus;
13 | private final String message;
14 |
15 | private ThrowableTranslator(final Throwable throwable) {
16 | this.httpStatus = getStatus(throwable);
17 | this.message = throwable.getMessage();
18 | }
19 |
20 | private HttpStatus getStatus(final Throwable error) {
21 | if (error instanceof InvalidParametersException) {
22 | return HttpStatus.BAD_REQUEST;
23 | } else if (error instanceof PathNotFoundException) {
24 | return HttpStatus.NOT_FOUND;
25 | } else if (error instanceof GeoLocationNotFoundException) {
26 | return HttpStatus.NOT_FOUND;
27 | } else if (error instanceof GetGeoLocationException) {
28 | if (error.getCause() instanceof InvalidParametersException)
29 | return HttpStatus.BAD_REQUEST;
30 | else
31 | return HttpStatus.INTERNAL_SERVER_ERROR;
32 | } else {
33 | return HttpStatus.INTERNAL_SERVER_ERROR;
34 | }
35 | }
36 |
37 | HttpStatus getHttpStatus() {
38 | return httpStatus;
39 | }
40 |
41 | String getMessage() {
42 | return message;
43 | }
44 |
45 | static Mono translate(final Mono throwable) {
46 | return throwable.flatMap(error -> Mono.just(new ThrowableTranslator(error)));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/ErrorResponse.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 |
4 | import com.fasterxml.jackson.annotation.JsonCreator;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 |
7 | public class ErrorResponse {
8 |
9 | private final String error;
10 |
11 | @JsonCreator
12 | public ErrorResponse(@JsonProperty("error") final String error) {
13 | this.error = error;
14 | }
15 |
16 | public String getError() {
17 | return error;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/GeoLocationResponse.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public final class GeoLocationResponse {
7 |
8 | private final Result results[];
9 | private final String status;
10 |
11 | @JsonCreator
12 | public GeoLocationResponse(@JsonProperty("results") Result[] results, @JsonProperty("status") String status) {
13 | this.results = results;
14 | this.status = status;
15 | }
16 |
17 | public String getStatus() {
18 | return status;
19 | }
20 |
21 | public Result[] getResults() {
22 | return results;
23 | }
24 |
25 | public static final class Result {
26 | final Address_component address_components[];
27 | final String formatted_address;
28 |
29 | public Geometry getGeometry() {
30 | return geometry;
31 | }
32 |
33 | final Geometry geometry;
34 | final String place_id;
35 | final String[] types;
36 |
37 | @JsonCreator
38 | public Result(@JsonProperty("address_components") Address_component[] address_components, @JsonProperty("formatted_address") String formatted_address, @JsonProperty("geometry") Geometry geometry, @JsonProperty("place_id") String place_id, @JsonProperty("types") String[] types) {
39 | this.address_components = address_components;
40 | this.formatted_address = formatted_address;
41 | this.geometry = geometry;
42 | this.place_id = place_id;
43 | this.types = types;
44 | }
45 |
46 | public static final class Address_component {
47 | final String long_name;
48 | final String short_name;
49 | final String[] types;
50 |
51 | @JsonCreator
52 | public Address_component(@JsonProperty("long_name") String long_name, @JsonProperty("short_name") String short_name, @JsonProperty("types") String[] types) {
53 | this.long_name = long_name;
54 | this.short_name = short_name;
55 | this.types = types;
56 | }
57 | }
58 |
59 | public static final class Geometry {
60 | final Bounds bounds;
61 |
62 | public Location getLocation() {
63 | return location;
64 | }
65 |
66 | final Location location;
67 | final String location_type;
68 | final Viewport viewport;
69 |
70 | @JsonCreator
71 | public Geometry(@JsonProperty("bounds") Bounds bounds, @JsonProperty("location") Location location, @JsonProperty("location_type") String location_type, @JsonProperty("viewport") Viewport viewport) {
72 | this.bounds = bounds;
73 | this.location = location;
74 | this.location_type = location_type;
75 | this.viewport = viewport;
76 | }
77 |
78 | public static final class Bounds {
79 | final Northeast northeast;
80 | final Southwest southwest;
81 |
82 | @JsonCreator
83 | public Bounds(@JsonProperty("northeast") Northeast northeast, @JsonProperty("southwest") Southwest southwest) {
84 | this.northeast = northeast;
85 | this.southwest = southwest;
86 | }
87 |
88 | public static final class Northeast {
89 | final double lat;
90 | final double lng;
91 |
92 | @JsonCreator
93 | public Northeast(@JsonProperty("lat") double lat, @JsonProperty("lng") double lng) {
94 | this.lat = lat;
95 | this.lng = lng;
96 | }
97 | }
98 |
99 | public static final class Southwest {
100 | final double lat;
101 | final double lng;
102 |
103 | @JsonCreator
104 | public Southwest(@JsonProperty("lat") double lat, @JsonProperty("lng") double lng) {
105 | this.lat = lat;
106 | this.lng = lng;
107 | }
108 | }
109 | }
110 |
111 | public static final class Location {
112 | public double getLat() {
113 | return lat;
114 | }
115 |
116 | public double getLng() {
117 | return lng;
118 | }
119 |
120 | final double lat;
121 | final double lng;
122 |
123 | @JsonCreator
124 | public Location(@JsonProperty("lat") double lat, @JsonProperty("lng") double lng) {
125 | this.lat = lat;
126 | this.lng = lng;
127 | }
128 | }
129 |
130 | public static final class Viewport {
131 | final Northeast northeast;
132 | final Southwest southwest;
133 |
134 | @JsonCreator
135 | public Viewport(@JsonProperty("northeast") Northeast northeast, @JsonProperty("southwest") Southwest southwest) {
136 | this.northeast = northeast;
137 | this.southwest = southwest;
138 | }
139 |
140 | public static final class Northeast {
141 | final double lat;
142 | final double lng;
143 |
144 | @JsonCreator
145 | public Northeast(@JsonProperty("lat") double lat, @JsonProperty("lng") double lng) {
146 | this.lat = lat;
147 | this.lng = lng;
148 | }
149 | }
150 |
151 | public static final class Southwest {
152 | final double lat;
153 | final double lng;
154 |
155 | @JsonCreator
156 | public Southwest(@JsonProperty("lat") double lat, @JsonProperty("lng") double lng) {
157 | this.lat = lat;
158 | this.lng = lng;
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/GeoTimesResponse.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public final class GeoTimesResponse {
7 |
8 | private final Results results;
9 | private final String status;
10 |
11 | @JsonCreator
12 | public GeoTimesResponse(@JsonProperty("results") Results results, @JsonProperty("status") String status) {
13 | this.results = results;
14 | this.status = status;
15 | }
16 |
17 | public Results getResults() {
18 | return results;
19 | }
20 |
21 | public String getStatus() {
22 | return status;
23 | }
24 |
25 | public static final class Results {
26 | public String getSunrise() {
27 | return sunrise;
28 | }
29 |
30 | public String getSunset() {
31 | return sunset;
32 | }
33 |
34 | final String sunrise;
35 | final String sunset;
36 | final String solar_noon;
37 | final long day_length;
38 | final String civil_twilight_begin;
39 | final String civil_twilight_end;
40 | final String nautical_twilight_begin;
41 | final String nautical_twilight_end;
42 | final String astronomical_twilight_begin;
43 | final String astronomical_twilight_end;
44 |
45 | @JsonCreator
46 | public Results(@JsonProperty("sunrise") String sunrise, @JsonProperty("sunset") String sunset, @JsonProperty("solar_noon") String solar_noon, @JsonProperty("day_length") long day_length, @JsonProperty("civil_twilight_begin") String civil_twilight_begin, @JsonProperty("civil_twilight_end") String civil_twilight_end, @JsonProperty("nautical_twilight_begin") String nautical_twilight_begin, @JsonProperty("nautical_twilight_end") String nautical_twilight_end, @JsonProperty("astronomical_twilight_begin") String astronomical_twilight_begin, @JsonProperty("astronomical_twilight_end") String astronomical_twilight_end) {
47 | this.sunrise = sunrise;
48 | this.sunset = sunset;
49 | this.solar_noon = solar_noon;
50 | this.day_length = day_length;
51 | this.civil_twilight_begin = civil_twilight_begin;
52 | this.civil_twilight_end = civil_twilight_end;
53 | this.nautical_twilight_begin = nautical_twilight_begin;
54 | this.nautical_twilight_end = nautical_twilight_end;
55 | this.astronomical_twilight_begin = astronomical_twilight_begin;
56 | this.astronomical_twilight_end = astronomical_twilight_end;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/GeographicCoordinates.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 |
7 | public final class GeographicCoordinates {
8 |
9 | public double getLatitude() {
10 | return latitude;
11 | }
12 |
13 | public double getLongitude() {
14 | return longitude;
15 | }
16 |
17 | private final double latitude;
18 | private final double longitude;
19 |
20 | @JsonCreator
21 | public GeographicCoordinates(@JsonProperty("latitude") double latitude, @JsonProperty("longitude") double longitude) {
22 | this.latitude = latitude;
23 | this.longitude = longitude;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/LocationRequest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 |
4 | import com.fasterxml.jackson.annotation.JsonCreator;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 |
7 | public class LocationRequest {
8 |
9 | private final String address;
10 |
11 | @JsonCreator
12 | public LocationRequest(@JsonProperty("address") final String address) {
13 | this.address = address;
14 | }
15 |
16 | public String getAddress() {
17 | return address;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/LocationResponse.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 |
4 | import com.fasterxml.jackson.annotation.JsonCreator;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 |
7 | public class LocationResponse {
8 |
9 | private final GeographicCoordinates geographicCoordinates;
10 | private final SunriseSunset sunriseSunset;
11 |
12 | @JsonCreator
13 | public LocationResponse(@JsonProperty("geographicCoordinates") final GeographicCoordinates geographicCoordinates,
14 | @JsonProperty("sunriseSunset") final SunriseSunset sunriseSunset) {
15 | this.geographicCoordinates = geographicCoordinates;
16 | this.sunriseSunset = sunriseSunset;
17 | }
18 |
19 | public GeographicCoordinates getGeographicCoordinates() {
20 | return geographicCoordinates;
21 | }
22 |
23 | public SunriseSunset getSunriseSunset() {
24 | return sunriseSunset;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/model/SunriseSunset.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class SunriseSunset {
7 |
8 | public String getSunrise() {
9 | return sunrise;
10 | }
11 |
12 | public String getSunset() {
13 | return sunset;
14 | }
15 |
16 | private final String sunrise;
17 | private final String sunset;
18 |
19 | @JsonCreator
20 | public SunriseSunset(@JsonProperty("sunrise") final String sunrise, @JsonProperty("sunset") final String sunset) {
21 | this.sunrise = sunrise;
22 | this.sunset = sunset;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/routers/ApiRouter.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.routers;
2 |
3 | import org.learning.by.example.reactive.microservices.handlers.ErrorHandler;
4 | import org.learning.by.example.reactive.microservices.handlers.ApiHandler;
5 | import org.springframework.web.reactive.function.server.RequestPredicates;
6 | import org.springframework.web.reactive.function.server.RouterFunction;
7 |
8 | import static org.springframework.http.MediaType.APPLICATION_JSON;
9 | import static org.springframework.web.reactive.function.server.RequestPredicates.*;
10 | import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
11 | import static org.springframework.web.reactive.function.server.RouterFunctions.route;
12 |
13 | class ApiRouter {
14 |
15 | private static final String API_PATH = "/api";
16 | private static final String LOCATION_PATH = "/location";
17 | private static final String ADDRESS_ARG = "/{address}";
18 | private static final String LOCATION_WITH_ADDRESS_PATH = LOCATION_PATH + ADDRESS_ARG;
19 |
20 | static RouterFunction> doRoute(final ApiHandler apiHandler, final ErrorHandler errorHandler) {
21 | return
22 | nest(path(API_PATH),
23 | nest(accept(APPLICATION_JSON),
24 | route(GET(LOCATION_WITH_ADDRESS_PATH), apiHandler::getLocation)
25 | .andRoute(POST(LOCATION_PATH), apiHandler::postLocation)
26 | ).andOther(route(RequestPredicates.all(), errorHandler::notFound))
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/routers/MainRouter.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.routers;
2 |
3 | import org.learning.by.example.reactive.microservices.handlers.ErrorHandler;
4 | import org.learning.by.example.reactive.microservices.handlers.ApiHandler;
5 | import org.springframework.web.reactive.function.server.RouterFunction;
6 |
7 | public class MainRouter {
8 |
9 | public static RouterFunction> doRoute(final ApiHandler handler, final ErrorHandler errorHandler) {
10 | return ApiRouter
11 | .doRoute(handler, errorHandler)
12 | .andOther(StaticRouter.doRoute());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/routers/StaticRouter.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.routers;
2 |
3 | import org.springframework.core.io.ClassPathResource;
4 | import org.springframework.web.reactive.function.server.RouterFunction;
5 |
6 | import static org.springframework.web.reactive.function.server.RouterFunctions.resources;
7 |
8 | class StaticRouter {
9 |
10 | private static final String ROUTE = "/**";
11 | private static final String PUBLIC = "public/";
12 |
13 | static RouterFunction> doRoute() {
14 | return resources(ROUTE, new ClassPathResource(PUBLIC));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/services/GeoLocationService.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.services;
2 |
3 | import org.learning.by.example.reactive.microservices.model.GeographicCoordinates;
4 | import reactor.core.publisher.Mono;
5 |
6 | public interface GeoLocationService {
7 |
8 | Mono fromAddress(Mono addressMono);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/services/GeoLocationServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.services;
2 |
3 | import org.learning.by.example.reactive.microservices.exceptions.GetGeoLocationException;
4 | import org.learning.by.example.reactive.microservices.exceptions.GeoLocationNotFoundException;
5 | import org.learning.by.example.reactive.microservices.exceptions.InvalidParametersException;
6 | import org.learning.by.example.reactive.microservices.model.GeographicCoordinates;
7 | import org.learning.by.example.reactive.microservices.model.GeoLocationResponse;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.web.reactive.function.client.WebClient;
10 | import reactor.core.publisher.Mono;
11 |
12 | public class GeoLocationServiceImpl implements GeoLocationService {
13 |
14 | private static final String OK_STATUS = "OK";
15 | private static final String ZERO_RESULTS = "ZERO_RESULTS";
16 | private static final String ERROR_GETTING_LOCATION = "error getting location";
17 | private static final String ERROR_LOCATION_WAS_NULL = "error location was null";
18 | private static final String ADDRESS_NOT_FOUND = "address not found";
19 | private static final String ADDRESS_PARAMETER = "?address=";
20 | private static final String MISSING_ADDRESS = "missing address";
21 | WebClient webClient;
22 | private final String endPoint;
23 |
24 | public GeoLocationServiceImpl(final String endPoint) {
25 | this.endPoint = endPoint;
26 | this.webClient = WebClient.create();
27 | }
28 |
29 | @Override
30 | public Mono fromAddress(final Mono addressMono) {
31 | return addressMono
32 | .transform(this::buildUrl)
33 | .transform(this::get)
34 | .onErrorResume(throwable -> Mono.error(new GetGeoLocationException(ERROR_GETTING_LOCATION, throwable)))
35 | .transform(this::geometryLocation);
36 | }
37 |
38 | Mono buildUrl(final Mono addressMono) {
39 | return addressMono.flatMap(address -> {
40 | if (address.equals("")) {
41 | return Mono.error(new InvalidParametersException(MISSING_ADDRESS));
42 | }
43 | return Mono.just(endPoint.concat(ADDRESS_PARAMETER).concat(address));
44 | });
45 | }
46 |
47 | Mono get(final Mono urlMono) {
48 | return urlMono.flatMap(url -> webClient
49 | .get()
50 | .uri(url)
51 | .accept(MediaType.APPLICATION_JSON)
52 | .exchange()
53 | .flatMap(clientResponse -> clientResponse.bodyToMono(GeoLocationResponse.class)));
54 | }
55 |
56 | Mono geometryLocation(final Mono geoLocationResponseMono) {
57 | return geoLocationResponseMono.flatMap(geoLocationResponse -> {
58 | if (geoLocationResponse.getStatus() != null) {
59 | switch (geoLocationResponse.getStatus()) {
60 | case OK_STATUS:
61 | return Mono.just(
62 | new GeographicCoordinates(geoLocationResponse.getResults()[0].getGeometry().getLocation().getLat(),
63 | geoLocationResponse.getResults()[0].getGeometry().getLocation().getLng()));
64 | case ZERO_RESULTS:
65 | return Mono.error(new GeoLocationNotFoundException(ADDRESS_NOT_FOUND));
66 | default:
67 | return Mono.error(new GetGeoLocationException(ERROR_GETTING_LOCATION));
68 | }
69 | } else {
70 | return Mono.error(new GetGeoLocationException(ERROR_LOCATION_WAS_NULL));
71 | }
72 | }
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/services/SunriseSunsetService.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.services;
2 |
3 | import org.learning.by.example.reactive.microservices.model.GeographicCoordinates;
4 | import org.learning.by.example.reactive.microservices.model.SunriseSunset;
5 | import reactor.core.publisher.Mono;
6 |
7 | public interface SunriseSunsetService {
8 |
9 | Mono fromGeographicCoordinates(Mono geographicCoordinatesMono);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/learning/by/example/reactive/microservices/services/SunriseSunsetServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.services;
2 |
3 | import org.learning.by.example.reactive.microservices.exceptions.GetSunriseSunsetException;
4 | import org.learning.by.example.reactive.microservices.model.GeographicCoordinates;
5 | import org.learning.by.example.reactive.microservices.model.SunriseSunset;
6 | import org.learning.by.example.reactive.microservices.model.GeoTimesResponse;
7 | import org.springframework.http.MediaType;
8 | import org.springframework.web.reactive.function.client.WebClient;
9 | import reactor.core.publisher.Mono;
10 |
11 | public class SunriseSunsetServiceImpl implements SunriseSunsetService {
12 |
13 | private static final String BEGIN_PARAMETERS = "?";
14 | private static final String NEXT_PARAMETER = "&";
15 | private static final String EQUALS = "=";
16 | private static final String LATITUDE_PARAMETER = "lat" + EQUALS;
17 | private static final String LONGITUDE_PARAMETER = "lng" + EQUALS;
18 | private static final String DATE_PARAMETER = "date" + EQUALS;
19 | private static final String TODAY_DATE = "today";
20 | private static final String FORMATTED_PARAMETER = "formatted" + EQUALS;
21 | private static final String NOT_FORMATTED = "0";
22 | private static final String ERROR_GETTING_DATA = "error getting sunrise and sunset";
23 | private static final String SUNRISE_RESULT_NOT_OK = "sunrise and sunrise result was not OK";
24 | private static final String STATUS_OK = "OK";
25 |
26 | WebClient webClient;
27 | private final String endPoint;
28 |
29 | public SunriseSunsetServiceImpl(final String endPoint) {
30 | this.endPoint = endPoint;
31 | this.webClient = WebClient.create();
32 | }
33 |
34 | @Override
35 | public Mono fromGeographicCoordinates(Mono location) {
36 | return location
37 | .transform(this::buildUrl)
38 | .transform(this::get)
39 | .onErrorResume(throwable -> Mono.error(new GetSunriseSunsetException(ERROR_GETTING_DATA, throwable)))
40 | .transform(this::createResult);
41 | }
42 |
43 | Mono buildUrl(final Mono geographicCoordinatesMono) {
44 | return geographicCoordinatesMono.flatMap(geographicCoordinates -> Mono.just(endPoint
45 | .concat(BEGIN_PARAMETERS)
46 | .concat(LATITUDE_PARAMETER).concat(Double.toString(geographicCoordinates.getLatitude()))
47 | .concat(NEXT_PARAMETER)
48 | .concat(LONGITUDE_PARAMETER).concat(Double.toString(geographicCoordinates.getLongitude()))
49 | .concat(NEXT_PARAMETER)
50 | .concat(DATE_PARAMETER).concat(TODAY_DATE)
51 | .concat(NEXT_PARAMETER)
52 | .concat(FORMATTED_PARAMETER).concat(NOT_FORMATTED)
53 | ));
54 | }
55 |
56 | Mono get(final Mono monoUrl) {
57 | return monoUrl.flatMap(url -> webClient
58 | .get()
59 | .uri(url)
60 | .accept(MediaType.APPLICATION_JSON)
61 | .exchange()
62 | .flatMap(clientResponse -> clientResponse.bodyToMono(GeoTimesResponse.class)));
63 | }
64 |
65 | Mono createResult(final Mono geoTimesResponseMono) {
66 | return geoTimesResponseMono.flatMap(geoTimesResponse -> {
67 | if ((geoTimesResponse.getStatus() != null) && (geoTimesResponse.getStatus().equals(STATUS_OK))) {
68 | return Mono.just(new SunriseSunset(geoTimesResponse.getResults().getSunrise(),
69 | geoTimesResponse.getResults().getSunset()));
70 | } else {
71 | return Mono.error(new GetSunriseSunsetException(SUNRISE_RESULT_NOT_OK));
72 | }
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | logging.level.root: INFO
2 | GeoLocationServiceImpl:
3 | endPoint: "https://maps.googleapis.com/maps/api/geocode/json"
4 | SunriseSunsetServiceImpl:
5 | endPoint: "https://api.sunrise-sunset.org/json"
6 |
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | _ _ _ _____ _
2 | | | (_) | | | ___| | |
3 | | | ___ __ _ _ __ _ __ _ _ __ __ _| |__ _ _| |____ ____ _ _ __ ___ _ __ | | ___
4 | | | / _ \/ _` | '__| '_ \| | '_ \ / _` | '_ \| | | | __\ \/ / _` | '_ ` _ \| '_ \| |/ _ \
5 | | |___| __/ (_| | | | | | | | | | | (_| | |_) | |_| | |___> < (_| | | | | | | |_) | | __/
6 | \_____/\___|\__,_|_| |_| |_|_|_| |_|\__, |_.__/ \__, \____/_/\_\__,_|_| |_| |_| .__/|_|\___|
7 | __/ | __/ | | |
8 | |___/ |___/ |_|
9 |
10 | An educational project to learn reactive programming with Spring 5
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/public/api.yaml:
--------------------------------------------------------------------------------
1 | swagger: "2.0"
2 | info:
3 | description: "This is the API for the Reactive Micro Service example"
4 | version: "1.0.0"
5 | title: "Reactive Micro Services API"
6 | contact:
7 | url: "https://github.com/LearningByExample/reactive-ms-example"
8 | host: "localhost:8080"
9 | basePath: "/api"
10 | tags:
11 | - name: "location"
12 | description: "geo location services"
13 | schemes:
14 | - "http"
15 | paths:
16 | /location/{address}:
17 | get:
18 | tags:
19 | - "location"
20 | summary: "get latitude, longitude, sunrise and sunset from an address parameter"
21 | produces:
22 | - "application/json"
23 | parameters:
24 | - name: "address"
25 | in: "path"
26 | required: true
27 | type: "string"
28 | responses:
29 | 200:
30 | description: "successful operation"
31 | schema:
32 | $ref: "#/definitions/LocationResponse"
33 | /location:
34 | post:
35 | tags:
36 | - "location"
37 | summary: "get latitude, longitude, sunrise and sunset from an LocationRequest object"
38 | consumes:
39 | - "application/json"
40 | produces:
41 | - "application/json"
42 | parameters:
43 | - in: "body"
44 | name: "body"
45 | description: "LocationRequest object that has an address"
46 | required: true
47 | schema:
48 | $ref: "#/definitions/LocationRequest"
49 | responses:
50 | 200:
51 | description: "successful operation"
52 | schema:
53 | $ref: "#/definitions/LocationResponse"
54 | definitions:
55 | LocationRequest:
56 | type: "object"
57 | properties:
58 | address:
59 | type: "string"
60 | LocationResponse:
61 | type: "object"
62 | properties:
63 | geographicCoordinates:
64 | type: "object"
65 | properties:
66 | latitude:
67 | type: "number"
68 | format: "double"
69 | longitude:
70 | type: "number"
71 | format: "double"
72 | sunriseSunset:
73 | type: "object"
74 | properties:
75 | sunrise:
76 | type: "string"
77 | format: "date-time"
78 | description: "ISO 8601 UTC without summer time adjustment"
79 | sunset:
80 | type: "string"
81 | format: "date-time"
82 | description: "ISO 8601 UTC without summer time adjustment"
83 |
--------------------------------------------------------------------------------
/src/main/resources/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LearningByExample/reactive-ms-example/c40d5855ce2c5e0b6dfb06aa4a421ae9560d6548/src/main/resources/public/favicon-16x16.png
--------------------------------------------------------------------------------
/src/main/resources/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LearningByExample/reactive-ms-example/c40d5855ce2c5e0b6dfb06aa4a421ae9560d6548/src/main/resources/public/favicon-32x32.png
--------------------------------------------------------------------------------
/src/main/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
11 |
30 |
31 |
32 |
33 |
34 |
67 |
68 |
69 |
70 |
71 |
72 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/main/resources/public/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
54 |
--------------------------------------------------------------------------------
/src/main/resources/public/swagger-ui-bundle.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;AAu/FA;AA6+FA;;;;;;;;;;;;;;;;;;;;;;;;;;AAmTA;;;;;;AAoIA;AAi7FA;AAmtCA;AAi0IA;AA2pJA;AA+uFA;AA2rGA;AAgiFA;AA0rFA;AAk9CA;AA2hDA;AA4rCA;AAi6EA;;;;;AA2gCA;AA02JA;;;;;;;;;;;;;;AAuyEA;AA4mIA;AAquJA;AAwsHA;AA2mGA;AAiiEA;AAq4DA;AA+2DA;AAqlBA;;;;;;AAilFA;AAs1FA;;;;;AAy3CA;AA2qFA;AAw2CA;AAwkCA;AAs/CA;AA4kFA;AAy1FA;;;;;;;;;AAm5CA;AA2zIA;AAk4DA;AAolDA","sourceRoot":""}
--------------------------------------------------------------------------------
/src/main/resources/public/swagger-ui-standalone-preset.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIStandalonePreset=t():e.SwaggerUIStandalonePreset=t()}(this,function(){return function(e){function t(r){if(o[r])return o[r].exports;var n=o[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var o={};return t.m=e,t.c=o,t.p="/dist",t(0)}([function(e,t,o){e.exports=o(1)},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var n=o(2),i=r(n);o(33);var a=o(37),s=r(a),l=[s.default,function(){return{components:{StandaloneLayout:i.default}}}];e.exports=l},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var o=0;o1){for(var m=Array(b),x=0;x1){for(var h=Array(w),y=0;y>",j={array:u("array"),bool:u("boolean"),func:u("function"),number:u("number"),object:u("object"),string:u("string"),symbol:u("symbol"),any:c(),arrayOf:f,element:d(),instanceOf:g,node:w(),objectOf:m,oneOf:b,oneOfType:x,shape:h};return l.prototype=Error.prototype,j.checkPropTypes=a,j.PropTypes=j,j}},function(e,t){"use strict";var o="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=o},function(e,t,o){"use strict";function r(e,t,o,r,n){}e.exports=r},function(e,t){"use strict";e.exports="15.5.4"},function(e,t,o){"use strict";function r(e){return i.isValidElement(e)?void 0:n("143"),e}var n=o(8),i=o(10);o(9);e.exports=r},function(e,t,o){var r=o(34);"string"==typeof r&&(r=[[e.id,r,""]]);o(36)(r,{});r.locals&&(e.exports=r.locals)},function(e,t,o){t=e.exports=o(35)(),t.push([e.id,"@charset \"UTF-8\";.swagger-ui html{box-sizing:border-box}.swagger-ui *,.swagger-ui :after,.swagger-ui :before{box-sizing:inherit}.swagger-ui body{margin:0;background:#fafafa}.swagger-ui .wrapper{width:100%;max-width:1460px;margin:0 auto;padding:0 20px}.swagger-ui .opblock-tag-section{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .opblock-tag{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 20px 10px 10px;cursor:pointer;transition:all .2s;border-bottom:1px solid rgba(59,65,81,.3);-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{font-size:24px;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock-tag.no-desc span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock-tag svg{transition:all .4s}.swagger-ui .opblock-tag small{font-size:14px;font-weight:400;padding:0 10px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parаmeter__type{font-size:12px;padding:5px 0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .view-line-link{position:relative;top:3px;width:20px;margin:0 5px;cursor:pointer;transition:all .5s}.swagger-ui .opblock{margin:0 0 15px;border:1px solid #000;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.19)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{padding:8px 20px;background:hsla(0,0%,100%,.8);box-shadow:0 1px 2px rgba(0,0,0,.1)}.swagger-ui .opblock .opblock-section-header,.swagger-ui .opblock .opblock-section-header label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-section-header label{font-size:12px;font-weight:700;margin:0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-section-header label span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{font-size:14px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary-method{font-size:14px;font-weight:700;min-width:80px;padding:6px 15px;text-align:center;border-radius:3px;background:#000;text-shadow:0 1px 0 rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 10px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-summary-path .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated .view-line-link{position:relative;top:2px;width:0;margin:0;cursor:pointer;transition:all .5s}.swagger-ui .opblock .opblock-summary-path:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated:hover .view-line-link{width:18px;margin:0 5px}.swagger-ui .opblock .opblock-summary-path__deprecated{text-decoration:line-through}.swagger-ui .opblock .opblock-summary-description{font-size:13px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary{display:-webkit-box;display:-ms-flexbox;display:flex;padding:5px;cursor:pointer;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock.opblock-post{border-color:#49cc90;background:rgba(73,204,144,.1)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-put{border-color:#fca130;background:rgba(252,161,48,.1)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-delete{border-color:#f93e3e;background:rgba(249,62,62,.1)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-get{border-color:#61affe;background:rgba(97,175,254,.1)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-patch{border-color:#50e3c2;background:rgba(80,227,194,.1)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-head{border-color:#9012fe;background:rgba(144,18,254,.1)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-options{border-color:#0d5aa7;background:rgba(13,90,167,.1)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{opacity:.6;border-color:#ebebeb;background:hsla(0,0%,92%,.1)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .tab{display:-webkit-box;display:-ms-flexbox;display:flex;margin:20px 0 10px;padding:0;list-style:none}.swagger-ui .tab li{font-size:12px;min-width:100px;min-width:90px;padding:0;cursor:pointer;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .tab li:first-of-type{position:relative;padding-left:0}.swagger-ui .tab li:first-of-type:after{position:absolute;top:0;right:6px;width:1px;height:100%;content:\"\";background:rgba(0,0,0,.2)}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-title_normal{padding:15px 20px}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4{font-size:12px;margin:0 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-title_normal p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{width:100%;padding:8px 40px}.swagger-ui .body-param-options{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{font-size:12px;margin:10px 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#999}.swagger-ui .response-col_description__inner span{font-size:12px;font-style:italic;display:block;margin:10px 0;padding:10px;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .response-col_description__inner span p{margin:0}.swagger-ui .opblock-body pre{font-size:12px;margin:0;padding:10px;white-space:pre-wrap;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .opblock-body pre span{color:#fff!important}.swagger-ui .opblock-body pre .headerline{display:block}.swagger-ui .scheme-container{margin:0 0 20px;padding:30px 0;background:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .scheme-container .schemes{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .scheme-container .schemes>label{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .loading-container{padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:\"loading\";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:\"\";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;transition:all .3s;border:2px solid #888;border-radius:4px;background:transparent;box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite;color:#fff;border-color:#4990e2}@-webkit-keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}@keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#444}.swagger-ui .expand-methods svg{transition:all .3s;fill:#777}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui .opblock-body select{min-width:230px}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}.swagger-ui input[type=email].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;transition:opacity .5s;color:#333}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E\") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:\"\";background:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E\") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models.is-open h4 svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:10px 20px 10px 10px;cursor:pointer;transition:all .2s;font-family:Titillium Web,sans-serif;color:#777;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui section.models h4 svg{transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#777}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#555}.swagger-ui span>span.model,.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#999}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:100px;padding:0}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{width:20%;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:\"required\";color:rgba(255,0,0,.6)}.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:#888}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 30px;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper{-ms-flex-align:center}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;max-width:300px;text-decoration:none;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-align:center;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:3;-ms-flex:3;flex:3}.swagger-ui .topbar .download-url-wrapper input[type=text]{width:100%;min-width:350px;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 40px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info p{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#666}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}",""]);
7 | },function(e,t){e.exports=function(){var e=[];return e.toString=function(){for(var e=[],t=0;t=0&&h.splice(t,1)}function s(e){var t=document.createElement("style");return t.type="text/css",i(e,t),t}function l(e){var t=document.createElement("link");return t.rel="stylesheet",i(e,t),t}function p(e,t){var o,r,n;if(t.singleton){var i=w++;o=x||(x=s(t)),r=u.bind(null,o,i,!1),n=u.bind(null,o,i,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(o=l(t),r=f.bind(null,o),n=function(){a(o),o.href&&URL.revokeObjectURL(o.href)}):(o=s(t),r=c.bind(null,o),n=function(){a(o)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else n()}}function u(e,t,o,r){var n=o?"":r.css;if(e.styleSheet)e.styleSheet.cssText=y(t,n);else{var i=document.createTextNode(n),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(i,a[t]):e.appendChild(i)}}function c(e,t){var o=t.css,r=t.media;t.sourceMap;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=o;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(o))}}function f(e,t){var o=t.css,r=(t.media,t.sourceMap);r&&(o+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(r))))+" */");var n=new Blob([o],{type:"text/css"}),i=e.href;e.href=URL.createObjectURL(n),i&&URL.revokeObjectURL(i)}var d={},g=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},b=g(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),m=g(function(){return document.head||document.getElementsByTagName("head")[0]}),x=null,w=0,h=[];e.exports=function(e,t){t=t||{},"undefined"==typeof t.singleton&&(t.singleton=b()),"undefined"==typeof t.insertAt&&(t.insertAt="bottom");var o=n(e);return r(o,t),function(e){for(var i=[],a=0;alabel{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .loading-container{padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:"loading";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:"";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;transition:all .3s;border:2px solid #888;border-radius:4px;background:transparent;box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite;color:#fff;border-color:#4990e2}@-webkit-keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}@keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#444}.swagger-ui .expand-methods svg{transition:all .3s;fill:#777}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui .opblock-body select{min-width:230px}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}.swagger-ui input[type=email].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;transition:opacity .5s;color:#333}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:"";background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models.is-open h4 svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:10px 20px 10px 10px;cursor:pointer;transition:all .2s;font-family:Titillium Web,sans-serif;color:#777;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui section.models h4 svg{transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#777}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#555}.swagger-ui span>span.model,.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#999}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:100px;padding:0}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{width:20%;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:"required";color:rgba(255,0,0,.6)}.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:#888}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 30px;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper{-ms-flex-align:center}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;max-width:300px;text-decoration:none;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-align:center;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:3;-ms-flex:3;flex:3}.swagger-ui .topbar .download-url-wrapper input[type=text]{width:100%;min-width:350px;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 40px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info p{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#666}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.swagger-ui .Resizer.vertical.disabled{display:none}
2 | /*# sourceMappingURL=swagger-ui.css.map*/
--------------------------------------------------------------------------------
/src/main/resources/public/swagger-ui.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
--------------------------------------------------------------------------------
/src/main/resources/public/swagger-ui.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAwxCA;AAoyHA;AAuxHA;AAy4FA;AA2sCA;AAmgCA;AA0iCA;AA+3BA","sourceRoot":""}
--------------------------------------------------------------------------------
/src/test/java/org/learning/by/example/reactive/microservices/application/ReactiveMsApplicationTest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.application;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.DisplayName;
5 | import org.junit.jupiter.api.Test;
6 | import org.learning.by.example.reactive.microservices.model.LocationRequest;
7 | import org.learning.by.example.reactive.microservices.model.LocationResponse;
8 | import org.learning.by.example.reactive.microservices.test.BasicIntegrationTest;
9 | import org.learning.by.example.reactive.microservices.test.tags.SystemTest;
10 | import org.springframework.boot.web.server.LocalServerPort;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.*;
14 |
15 |
16 | @SystemTest
17 | @DisplayName("ReactiveMsApplication System Tests")
18 | class ReactiveMsApplicationTest extends BasicIntegrationTest {
19 |
20 | private static final String LOCATION_PATH = "/api/location";
21 | private static final String ADDRESS_ARG = "{address}";
22 | private static final String GOOGLE_ADDRESS = "1600 Amphitheatre Parkway, Mountain View, CA";
23 |
24 | @LocalServerPort
25 | private int port;
26 |
27 | @BeforeEach
28 | void setup() {
29 | bindToServerPort(port);
30 | }
31 |
32 | @Test
33 | @DisplayName("get location from URL")
34 | void getLocationTest() {
35 | final LocationResponse response = get(
36 | builder -> builder.path(LOCATION_PATH).path("/").path(ADDRESS_ARG).build(GOOGLE_ADDRESS),
37 | LocationResponse.class);
38 |
39 | assertThat(response.getGeographicCoordinates(), not(nullValue()));
40 | assertThat(response.getGeographicCoordinates().getLatitude(), not(nullValue()));
41 | assertThat(response.getGeographicCoordinates().getLongitude(), not(nullValue()));
42 |
43 | assertThat(response.getSunriseSunset(), not(nullValue()));
44 | assertThat(response.getSunriseSunset().getSunrise(), not(isEmptyOrNullString()));
45 | assertThat(response.getSunriseSunset().getSunset(), not(isEmptyOrNullString()));
46 | }
47 |
48 | @Test
49 | @DisplayName("post location")
50 | void postLocationTest() {
51 | final LocationResponse response = post(
52 | builder -> builder.path(LOCATION_PATH).build(),
53 | new LocationRequest(GOOGLE_ADDRESS),
54 | LocationResponse.class);
55 |
56 | assertThat(response.getGeographicCoordinates(), not(nullValue()));
57 | assertThat(response.getGeographicCoordinates().getLatitude(), not(nullValue()));
58 | assertThat(response.getGeographicCoordinates().getLongitude(), not(nullValue()));
59 |
60 | assertThat(response.getSunriseSunset(), not(nullValue()));
61 | assertThat(response.getSunriseSunset().getSunrise(), not(isEmptyOrNullString()));
62 | assertThat(response.getSunriseSunset().getSunset(), not(isEmptyOrNullString()));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/org/learning/by/example/reactive/microservices/application/ReactiveMsApplicationUnitTest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.application;
2 |
3 | import org.junit.jupiter.api.DisplayName;
4 | import org.junit.jupiter.api.Test;
5 | import org.learning.by.example.reactive.microservices.test.tags.UnitTest;
6 |
7 | @UnitTest
8 | @DisplayName("ReactiveMsApplication Unit Tests")
9 | class ReactiveMsApplicationUnitTest {
10 |
11 | @Test
12 | void ReactiveMsApplication() {
13 | final String[] args = {};
14 | ReactiveMsApplication.main(args);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/org/learning/by/example/reactive/microservices/handlers/ApiHandlerTest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.junit.jupiter.api.DisplayName;
4 | import org.junit.jupiter.api.Test;
5 | import org.learning.by.example.reactive.microservices.exceptions.GeoLocationNotFoundException;
6 | import org.learning.by.example.reactive.microservices.exceptions.GetGeoLocationException;
7 | import org.learning.by.example.reactive.microservices.exceptions.GetSunriseSunsetException;
8 | import org.learning.by.example.reactive.microservices.model.*;
9 | import org.learning.by.example.reactive.microservices.services.GeoLocationService;
10 | import org.learning.by.example.reactive.microservices.services.SunriseSunsetService;
11 | import org.learning.by.example.reactive.microservices.test.HandlersHelper;
12 | import org.learning.by.example.reactive.microservices.test.tags.UnitTest;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 | import org.springframework.boot.test.mock.mockito.SpyBean;
15 | import org.springframework.http.HttpStatus;
16 | import org.springframework.web.reactive.function.server.ServerRequest;
17 | import org.springframework.web.reactive.function.server.ServerResponse;
18 | import reactor.core.publisher.Mono;
19 |
20 | import static org.hamcrest.MatcherAssert.assertThat;
21 | import static org.hamcrest.Matchers.is;
22 | import static org.mockito.ArgumentMatchers.any;
23 | import static org.mockito.Mockito.*;
24 |
25 | @UnitTest
26 | @DisplayName("ApiHandler Unit Tests")
27 | class ApiHandlerTest {
28 |
29 | private static final String ADDRESS_VARIABLE = "address";
30 | private static final String GOOGLE_ADDRESS = "1600 Amphitheatre Parkway, Mountain View, CA";
31 | private static final String SUNRISE_TIME = "12:55:17 PM";
32 | private static final String SUNSET_TIME = "3:14:28 AM";
33 | private static final double GOOGLE_LAT = 37.4224082;
34 | private static final double GOOGLE_LNG = -122.0856086;
35 | private static final String NOT_FOUND = "not found";
36 | private static final String CANT_GET_LOCATION = "cant get location";
37 | private static final String CANT_GET_SUNRISE_SUNSET = "can't get sunrise sunset";
38 |
39 | private static final Mono GOOGLE_LOCATION = Mono.just(new GeographicCoordinates(GOOGLE_LAT, GOOGLE_LNG));
40 | private static final Mono SUNRISE_SUNSET = Mono.just(new SunriseSunset(SUNRISE_TIME, SUNSET_TIME));
41 | private static final Mono LOCATION_NOT_FOUND = Mono.error(new GeoLocationNotFoundException(NOT_FOUND));
42 | private static final Mono LOCATION_EXCEPTION = Mono.error(new GetGeoLocationException(CANT_GET_LOCATION));
43 | private static final Mono SUNRISE_SUNSET_ERROR = Mono.error(new GetSunriseSunsetException(CANT_GET_SUNRISE_SUNSET));
44 |
45 |
46 | @Autowired
47 | private ApiHandler apiHandler;
48 |
49 | @SpyBean
50 | private GeoLocationService geoLocationService;
51 |
52 | @SpyBean
53 | private SunriseSunsetService sunriseSunsetService;
54 |
55 | private Mono getData(final GeographicCoordinates ignore) {
56 | return SUNRISE_SUNSET;
57 | }
58 |
59 | @Test
60 | void combineTest() {
61 | GOOGLE_LOCATION.zipWhen(this::getData, LocationResponse::new)
62 | .subscribe(this::verifyLocationResponse);
63 | }
64 |
65 | private void verifyLocationResponse(final LocationResponse locationResponse) {
66 |
67 | assertThat(locationResponse.getGeographicCoordinates().getLatitude(), is(GOOGLE_LAT));
68 | assertThat(locationResponse.getGeographicCoordinates().getLongitude(), is(GOOGLE_LNG));
69 |
70 | assertThat(locationResponse.getSunriseSunset().getSunrise(), is(SUNRISE_TIME));
71 | assertThat(locationResponse.getSunriseSunset().getSunset(), is(SUNSET_TIME));
72 | }
73 |
74 | @Test
75 | void serverResponseTest() {
76 | GOOGLE_LOCATION.zipWhen(this::getData, LocationResponse::new)
77 | .transform(apiHandler::serverResponse).subscribe(this::verifyServerResponse);
78 | }
79 |
80 | private void verifyServerResponse(final ServerResponse serverResponse) {
81 |
82 | assertThat(serverResponse.statusCode(), is(HttpStatus.OK));
83 |
84 | final LocationResponse locationResponse = HandlersHelper.extractEntity(serverResponse, LocationResponse.class);
85 |
86 | verifyLocationResponse(locationResponse);
87 | }
88 |
89 | @Test
90 | void buildResponseTest() {
91 | final ServerRequest serverRequest = mock(ServerRequest.class);
92 | when(serverRequest.pathVariable(ADDRESS_VARIABLE)).thenReturn(GOOGLE_ADDRESS);
93 |
94 | doReturn(GOOGLE_LOCATION).when(geoLocationService).fromAddress(any());
95 | doReturn(SUNRISE_SUNSET).when(sunriseSunsetService).fromGeographicCoordinates(any());
96 |
97 | Mono.just(GOOGLE_ADDRESS).transform(apiHandler::buildResponse).subscribe(this::verifyServerResponse);
98 |
99 | reset(geoLocationService);
100 | reset(sunriseSunsetService);
101 | }
102 |
103 | @Test
104 | void getLocationTest() {
105 | ServerRequest serverRequest = mock(ServerRequest.class);
106 | when(serverRequest.pathVariable(ADDRESS_VARIABLE)).thenReturn(GOOGLE_ADDRESS);
107 |
108 | doReturn(GOOGLE_LOCATION).when(geoLocationService).fromAddress(any());
109 | doReturn(SUNRISE_SUNSET).when(sunriseSunsetService).fromGeographicCoordinates(any());
110 |
111 | apiHandler.getLocation(serverRequest).subscribe(this::verifyServerResponse);
112 |
113 | reset(geoLocationService);
114 | reset(sunriseSunsetService);
115 | }
116 |
117 | @Test
118 | void postLocationTest() {
119 | ServerRequest serverRequest = mock(ServerRequest.class);
120 | when(serverRequest.bodyToMono(LocationRequest.class)).thenReturn(Mono.just(new LocationRequest(GOOGLE_ADDRESS)));
121 |
122 | doReturn(GOOGLE_LOCATION).when(geoLocationService).fromAddress(any());
123 | doReturn(SUNRISE_SUNSET).when(sunriseSunsetService).fromGeographicCoordinates(any());
124 |
125 | apiHandler.postLocation(serverRequest).subscribe(this::verifyServerResponse);
126 |
127 | reset(geoLocationService);
128 | reset(sunriseSunsetService);
129 | }
130 |
131 | @Test
132 | void getLocationNotFoundTest() {
133 | ServerRequest serverRequest = mock(ServerRequest.class);
134 | when(serverRequest.pathVariable(ADDRESS_VARIABLE)).thenReturn(GOOGLE_ADDRESS);
135 |
136 | doReturn(LOCATION_NOT_FOUND).when(geoLocationService).fromAddress(any());
137 | doReturn(SUNRISE_SUNSET).when(sunriseSunsetService).fromGeographicCoordinates(any());
138 |
139 | ServerResponse serverResponse = apiHandler.getLocation(serverRequest).block();
140 |
141 | assertThat(serverResponse.statusCode(), is(HttpStatus.NOT_FOUND));
142 |
143 | ErrorResponse error = HandlersHelper.extractEntity(serverResponse, ErrorResponse.class);
144 |
145 | assertThat(error.getError(), is(NOT_FOUND));
146 |
147 | reset(geoLocationService);
148 | reset(sunriseSunsetService);
149 | }
150 |
151 | @Test
152 | void getLocationErrorSunriseSunsetTest() {
153 | ServerRequest serverRequest = mock(ServerRequest.class);
154 | when(serverRequest.pathVariable(ADDRESS_VARIABLE)).thenReturn(GOOGLE_ADDRESS);
155 |
156 | doReturn(GOOGLE_LOCATION).when(geoLocationService).fromAddress(any());
157 | doReturn(SUNRISE_SUNSET_ERROR).when(sunriseSunsetService).fromGeographicCoordinates(any());
158 |
159 | ServerResponse serverResponse = apiHandler.getLocation(serverRequest).block();
160 |
161 | assertThat(serverResponse.statusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR));
162 |
163 | ErrorResponse error = HandlersHelper.extractEntity(serverResponse, ErrorResponse.class);
164 |
165 | assertThat(error.getError(), is(CANT_GET_SUNRISE_SUNSET));
166 |
167 | reset(geoLocationService);
168 | reset(sunriseSunsetService);
169 | }
170 |
171 | @Test
172 | void getLocationBothServiceErrorTest() {
173 | ServerRequest serverRequest = mock(ServerRequest.class);
174 | when(serverRequest.pathVariable(ADDRESS_VARIABLE)).thenReturn(GOOGLE_ADDRESS);
175 |
176 | doReturn(LOCATION_EXCEPTION).when(geoLocationService).fromAddress(any());
177 | doReturn(SUNRISE_SUNSET_ERROR).when(sunriseSunsetService).fromGeographicCoordinates(any());
178 |
179 | ServerResponse serverResponse = apiHandler.getLocation(serverRequest).block();
180 |
181 | assertThat(serverResponse.statusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR));
182 |
183 | ErrorResponse error = HandlersHelper.extractEntity(serverResponse, ErrorResponse.class);
184 |
185 | assertThat(error.getError(), is(CANT_GET_LOCATION));
186 |
187 | reset(geoLocationService);
188 | reset(sunriseSunsetService);
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/test/java/org/learning/by/example/reactive/microservices/handlers/ErrorHandlerTest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.junit.jupiter.api.DisplayName;
4 | import org.junit.jupiter.api.Test;
5 | import org.learning.by.example.reactive.microservices.exceptions.PathNotFoundException;
6 | import org.learning.by.example.reactive.microservices.model.ErrorResponse;
7 | import org.learning.by.example.reactive.microservices.test.HandlersHelper;
8 | import org.learning.by.example.reactive.microservices.test.tags.UnitTest;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.web.reactive.function.server.ServerResponse;
12 | import reactor.core.publisher.Mono;
13 |
14 | import java.util.function.Consumer;
15 |
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.Matchers.is;
18 |
19 |
20 | @UnitTest
21 | @DisplayName("ErrorHandler Unit Tests")
22 | class ErrorHandlerTest {
23 |
24 | private static final String NOT_FOUND = "not found";
25 |
26 | @Autowired
27 | private ErrorHandler errorHandler;
28 |
29 | @Test
30 | void notFoundTest() {
31 | errorHandler.notFound(null)
32 | .subscribe(checkResponse(HttpStatus.NOT_FOUND, NOT_FOUND));
33 | }
34 |
35 | @Test
36 | void throwableErrorTest() {
37 | errorHandler.throwableError(new PathNotFoundException(NOT_FOUND))
38 | .subscribe(checkResponse(HttpStatus.NOT_FOUND, NOT_FOUND));
39 | }
40 |
41 | @Test
42 | void getResponseTest() {
43 | Mono.just(new PathNotFoundException(NOT_FOUND)).transform(errorHandler::getResponse)
44 | .subscribe(checkResponse(HttpStatus.NOT_FOUND, NOT_FOUND));
45 | }
46 |
47 | private static Consumer checkResponse(final HttpStatus httpStatus, final String message) {
48 | return serverResponse -> {
49 | assertThat(serverResponse.statusCode(), is(httpStatus));
50 | assertThat(HandlersHelper.extractEntity(serverResponse, ErrorResponse.class).getError(), is(message));
51 | };
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/org/learning/by/example/reactive/microservices/handlers/ThrowableTranslatorTest.java:
--------------------------------------------------------------------------------
1 | package org.learning.by.example.reactive.microservices.handlers;
2 |
3 | import org.hamcrest.Description;
4 | import org.hamcrest.DiagnosingMatcher;
5 | import org.hamcrest.Factory;
6 | import org.junit.jupiter.api.DisplayName;
7 | import org.junit.jupiter.api.Test;
8 | import org.learning.by.example.reactive.microservices.exceptions.*;
9 | import org.learning.by.example.reactive.microservices.test.tags.UnitTest;
10 | import org.springframework.http.HttpStatus;
11 | import reactor.core.publisher.Mono;
12 |
13 | import java.lang.reflect.InvocationTargetException;
14 |
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 | import static org.hamcrest.Matchers.is;
17 |
18 | @UnitTest
19 | @DisplayName("ThrowableTranslator Unit Tests")
20 | class ThrowableTranslatorTest {
21 |
22 | @Factory
23 | private static DiagnosingMatcher