diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 129 | ||||
-rw-r--r-- | README.md | 268 | ||||
-rw-r--r-- | build.sbt | 165 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/Converters.scala (renamed from src/main/scala/xyz/driver/core/database/Converters.scala) | 2 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala (renamed from src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala) | 0 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/Repository.scala (renamed from src/main/scala/xyz/driver/core/database/Repository.scala) | 0 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala (renamed from src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala) | 0 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/database.scala (renamed from src/main/scala/xyz/driver/core/database/database.scala) | 0 | ||||
-rw-r--r-- | core-database/src/main/scala/xyz/driver/core/database/package.scala (renamed from src/main/scala/xyz/driver/core/database/package.scala) | 0 | ||||
-rw-r--r-- | core-database/src/test/scala/xyz/driver/core/database/DatabaseTest.scala (renamed from src/test/scala/xyz/driver/core/database/DatabaseTest.scala) | 2 | ||||
-rw-r--r-- | core-init/src/main/resources/deployed-logback.xml (renamed from src/main/resources/deployed-logback.xml) | 0 | ||||
-rw-r--r-- | core-init/src/main/resources/logback-test.xml (renamed from src/main/resources/logback-test.xml) | 0 | ||||
-rw-r--r-- | core-init/src/main/resources/logback.xml (renamed from src/main/resources/logback.xml) | 0 | ||||
-rw-r--r-- | core-init/src/main/resources/reference.conf | 26 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/AkkaBootable.scala (renamed from src/main/scala/xyz/driver/core/init/AkkaBootable.scala) | 2 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala (renamed from src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala) | 2 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/CloudServices.scala (renamed from src/main/scala/xyz/driver/core/init/CloudServices.scala) | 14 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/HttpApi.scala (renamed from src/main/scala/xyz/driver/core/init/HttpApi.scala) | 2 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/Platform.scala | 68 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/ProtobufApi.scala (renamed from src/main/scala/xyz/driver/core/init/ProtobufApi.scala) | 0 | ||||
-rw-r--r-- | core-init/src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala (renamed from src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/AliyunBus.scala (renamed from src/main/scala/xyz/driver/core/messaging/AliyunBus.scala) | 32 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/Bus.scala (renamed from src/main/scala/xyz/driver/core/messaging/Bus.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala (renamed from src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/GoogleBus.scala (renamed from src/main/scala/xyz/driver/core/messaging/GoogleBus.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/QueueBus.scala (renamed from src/main/scala/xyz/driver/core/messaging/QueueBus.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/StreamBus.scala (renamed from src/main/scala/xyz/driver/core/messaging/StreamBus.scala) | 2 | ||||
-rw-r--r-- | core-messaging/src/main/scala/xyz/driver/core/messaging/Topic.scala (renamed from src/main/scala/xyz/driver/core/messaging/Topic.scala) | 0 | ||||
-rw-r--r-- | core-messaging/src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala (renamed from src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala (renamed from src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala (renamed from src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/NoReporter.scala (renamed from src/main/scala/xyz/driver/core/reporting/NoReporter.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala (renamed from src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/Reporter.scala (renamed from src/main/scala/xyz/driver/core/reporting/Reporter.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala (renamed from src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala) | 0 | ||||
-rw-r--r-- | core-reporting/src/main/scala/xyz/driver/core/reporting/SpanContext.scala (renamed from src/main/scala/xyz/driver/core/reporting/SpanContext.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/reference.conf (renamed from src/main/resources/reference.conf) | 27 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/README.md (renamed from src/main/resources/swagger-ui-dist/README.md) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/absolute-path.js (renamed from src/main/resources/swagger-ui-dist/absolute-path.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/favicon-16x16.png (renamed from src/main/resources/swagger-ui-dist/favicon-16x16.png) | bin | 445 -> 445 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/favicon-32x32.png (renamed from src/main/resources/swagger-ui-dist/favicon-32x32.png) | bin | 1141 -> 1141 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/index.html (renamed from src/main/resources/swagger-ui-dist/index.html) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/index.js (renamed from src/main/resources/swagger-ui-dist/index.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/oauth2-redirect.html (renamed from src/main/resources/swagger-ui-dist/oauth2-redirect.html) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/package.json (renamed from src/main/resources/swagger-ui-dist/package.json) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js (renamed from src/main/resources/swagger-ui-dist/swagger-ui-bundle.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map (renamed from src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js (renamed from src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map (renamed from src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css (renamed from src/main/resources/swagger-ui-dist/swagger-ui.css) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css.map (renamed from src/main/resources/swagger-ui-dist/swagger-ui.css.map) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js (renamed from src/main/resources/swagger-ui-dist/swagger-ui.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js.map (renamed from src/main/resources/swagger-ui-dist/swagger-ui.js.map) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/.gitattributes (renamed from src/main/resources/swagger-ui/.gitattributes) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/css/print.css (renamed from src/main/resources/swagger-ui/css/print.css) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/css/reset.css (renamed from src/main/resources/swagger-ui/css/reset.css) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/css/screen.css (renamed from src/main/resources/swagger-ui/css/screen.css) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/css/typography.css (renamed from src/main/resources/swagger-ui/css/typography.css) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot) | bin | 22922 -> 22922 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf) | bin | 40513 -> 40513 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff) | bin | 25992 -> 25992 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2) | bin | 11480 -> 11480 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot) | bin | 22008 -> 22008 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf) | bin | 39069 -> 39069 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff) | bin | 24868 -> 24868 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 (renamed from src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2) | bin | 11304 -> 11304 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/explorer_icons.png (renamed from src/main/resources/swagger-ui/images/explorer_icons.png) | bin | 5763 -> 5763 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/favicon-16x16.png (renamed from src/main/resources/swagger-ui/images/favicon-16x16.png) | bin | 645 -> 645 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/favicon-32x32.png (renamed from src/main/resources/swagger-ui/images/favicon-32x32.png) | bin | 1654 -> 1654 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/favicon.ico (renamed from src/main/resources/swagger-ui/images/favicon.ico) | bin | 5430 -> 5430 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/logo_small.png (renamed from src/main/resources/swagger-ui/images/logo_small.png) | bin | 770 -> 770 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/pet_store_api.png (renamed from src/main/resources/swagger-ui/images/pet_store_api.png) | bin | 824 -> 824 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/throbber.gif (renamed from src/main/resources/swagger-ui/images/throbber.gif) | bin | 9257 -> 9257 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/images/wordnik_api.png (renamed from src/main/resources/swagger-ui/images/wordnik_api.png) | bin | 980 -> 980 bytes | |||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/index.html (renamed from src/main/resources/swagger-ui/index.html) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/backbone-min.js (renamed from src/main/resources/swagger-ui/lib/backbone-min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/handlebars-2.0.0.js (renamed from src/main/resources/swagger-ui/lib/handlebars-2.0.0.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/highlight.7.3.pack.js (renamed from src/main/resources/swagger-ui/lib/highlight.7.3.pack.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js (renamed from src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js (renamed from src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/jquery.slideto.min.js (renamed from src/main/resources/swagger-ui/lib/jquery.slideto.min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js (renamed from src/main/resources/swagger-ui/lib/jquery.wiggle.min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/marked.js (renamed from src/main/resources/swagger-ui/lib/marked.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/swagger-oauth.js (renamed from src/main/resources/swagger-ui/lib/swagger-oauth.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/underscore-min.js (renamed from src/main/resources/swagger-ui/lib/underscore-min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/lib/underscore-min.map (renamed from src/main/resources/swagger-ui/lib/underscore-min.map) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/o2c.html (renamed from src/main/resources/swagger-ui/o2c.html) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/swagger-ui.js (renamed from src/main/resources/swagger-ui/swagger-ui.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/resources/swagger-ui/swagger-ui.min.js (renamed from src/main/resources/swagger-ui/swagger-ui.min.js) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/auth.scala (renamed from src/main/scala/xyz/driver/core/auth.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/generators.scala (renamed from src/main/scala/xyz/driver/core/generators.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/json.scala (renamed from src/main/scala/xyz/driver/core/json.scala) | 18 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala (renamed from src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/DriverRoute.scala (renamed from src/main/scala/xyz/driver/core/rest/DriverRoute.scala) | 3 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala (renamed from src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/PatchDirectives.scala (renamed from src/main/scala/xyz/driver/core/rest/PatchDirectives.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala (renamed from src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/ProxyRoute.scala (renamed from src/main/scala/xyz/driver/core/rest/ProxyRoute.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/RestService.scala (renamed from src/main/scala/xyz/driver/core/rest/RestService.scala) | 6 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala (renamed from src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala (renamed from src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/Swagger.scala (renamed from src/main/scala/xyz/driver/core/rest/Swagger.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/Authorization.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/Authorization.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala (renamed from src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala (renamed from src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala (renamed from src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/directives/Directives.scala (renamed from src/main/scala/xyz/driver/core/rest/directives/Directives.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala (renamed from src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala) | 12 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala (renamed from src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/domain.scala | 57 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/errors/serviceException.scala (renamed from src/main/scala/xyz/driver/core/rest/errors/serviceException.scala) | 3 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala (renamed from src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/package.scala (renamed from src/main/scala/xyz/driver/core/rest/package.scala) | 62 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala (renamed from src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala (renamed from src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/main/scala/xyz/driver/core/swagger.scala (renamed from src/main/scala/xyz/driver/core/swagger.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/AuthTest.scala (renamed from src/test/scala/xyz/driver/core/AuthTest.scala) | 5 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/GeneratorsTest.scala (renamed from src/test/scala/xyz/driver/core/GeneratorsTest.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/JsonTest.scala (renamed from src/test/scala/xyz/driver/core/JsonTest.scala) | 15 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/TestTypes.scala (renamed from src/test/scala/xyz/driver/core/TestTypes.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala (renamed from src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala) | 15 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala (renamed from src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala) | 0 | ||||
-rw-r--r-- | core-rest/src/test/scala/xyz/driver/core/rest/RestTest.scala (renamed from src/test/scala/xyz/driver/core/rest/RestTest.scala) | 0 | ||||
-rw-r--r-- | core-storage/src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala (renamed from src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala) | 19 | ||||
-rw-r--r-- | core-storage/src/main/scala/xyz/driver/core/storage/BlobStorage.scala (renamed from src/main/scala/xyz/driver/core/storage/BlobStorage.scala) | 0 | ||||
-rw-r--r-- | core-storage/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala (renamed from src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala) | 0 | ||||
-rw-r--r-- | core-storage/src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala (renamed from src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala) | 0 | ||||
-rw-r--r-- | core-storage/src/main/scala/xyz/driver/core/storage/channelStreams.scala (renamed from src/main/scala/xyz/driver/core/storage/channelStreams.scala) | 0 | ||||
-rw-r--r-- | core-storage/src/test/scala/xyz/driver/core/BlobStorageTest.scala (renamed from src/test/scala/xyz/driver/core/BlobStorageTest.scala) | 0 | ||||
-rw-r--r-- | core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala | 79 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala | 28 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala | 18 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala | 60 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala | 70 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala | 26 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala | 46 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala | 74 | ||||
-rw-r--r-- | core-testkit/src/main/scala/xyz/driver/test/renames.scala | 46 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/DatabaseException.scala | 3 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/core.scala (renamed from src/main/scala/xyz/driver/core/core.scala) | 10 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/date.scala (renamed from src/main/scala/xyz/driver/core/date.scala) | 0 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/deprecations.scala | 5 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/domain.scala | 73 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/generators.scala | 143 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/tagging/tagging.scala (renamed from src/main/scala/xyz/driver/core/tagging/tagging.scala) | 0 | ||||
-rw-r--r-- | core-types/src/main/scala/xyz/driver/core/time.scala (renamed from src/main/scala/xyz/driver/core/time.scala) | 0 | ||||
-rw-r--r-- | core-types/src/test/scala/xyz/driver/core/CoreTest.scala (renamed from src/test/scala/xyz/driver/core/CoreTest.scala) | 0 | ||||
-rw-r--r-- | core-types/src/test/scala/xyz/driver/core/DateTest.scala (renamed from src/test/scala/xyz/driver/core/DateTest.scala) | 0 | ||||
-rw-r--r-- | core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala (renamed from src/test/scala/xyz/driver/core/PhoneNumberTest.scala) | 38 | ||||
-rw-r--r-- | core-types/src/test/scala/xyz/driver/core/TimeTest.scala (renamed from src/test/scala/xyz/driver/core/TimeTest.scala) | 0 | ||||
-rw-r--r-- | core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala (renamed from src/test/scala/xyz/driver/core/tagging/TaggingTest.scala) | 0 | ||||
-rw-r--r-- | core-util/src/main/scala/xyz/driver/core/Refresh.scala (renamed from src/main/scala/xyz/driver/core/Refresh.scala) | 0 | ||||
-rw-r--r-- | documentation/components.dot | 21 | ||||
-rw-r--r-- | documentation/components.svg | 133 | ||||
-rw-r--r-- | documentation/example/.gitignore | 1 | ||||
-rw-r--r-- | documentation/example/build.sbt | 6 | ||||
-rw-r--r-- | documentation/example/project/build.properties | 1 | ||||
-rw-r--r-- | documentation/example/project/plugins.sbt | 1 | ||||
-rw-r--r-- | documentation/example/src/main/scala/example/Main.scala | 21 | ||||
-rw-r--r-- | project/MiMaSettings.scala | 28 | ||||
-rw-r--r-- | project/build.properties | 2 | ||||
-rw-r--r-- | project/plugins.sbt | 3 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/database/MdcAsyncExecutor.scala | 53 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/domain.scala | 40 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/init/Platform.scala | 31 | ||||
-rw-r--r-- | src/test/scala/xyz/driver/core/DriverAppTest.scala (renamed from src/test/scala/xyz/driver/core/rest/DriverAppTest.scala) | 2 |
173 files changed, 1504 insertions, 516 deletions
diff --git a/.travis.yml b/.travis.yml index 7845eae..5ab8328 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,5 @@ scala: script: - echo 'credentials += Credentials("Artifactory Realm", "drivergrp.jfrog.io", "sbt-publisher", sys.env("ARTIFACTORY_PASSWORD"))' > project/credentials.sbt - - sbt clean +test + - sbt clean compile checkAbi +test - if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_TAG" =~ ^v[0-9].* ]]; then sbt publish; fi diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..313e712 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,129 @@ +# UNRELEASED + +This release represents a major version bump: many new features have +been added and large parts of the codebase have been refactored. +Although a lot of effort has gone into keeping API changes minimal, +this release is unfortunately not source or binary compatible with the +1.x versions. + +The following is a list of major changes. Please see the git commit +log `git log 1.x..v2.0.0` for details. + +## Structural Changes + +The core project has been split into many modules, as described in the +README, each having distinct functionality. + +In a nutshell, packages in `xyz.driver.core` have been moved to +separate sbt projects. For example, all classes contained in +`xyz.driver.core.messaging` have been moved to the project +`core-messaging`, and `xyz.driver.core.storage` to +`core-storage`. Thus, the modules providing utilities from the latest +1.x version are: + +- `core-database` +- `core-rest` +- `core-messaging` +- `core-storage` + +Along those functional modules, there are the supporting modules: + +- `core-types`, which contains common types, such as `time`, `date` + and IDs +- `core`, which contains the `app` initialization system and also + depends on all other modules, acting as an indirection for + backwards-compatibility. + +Furthermore, some new modules have been added which will be explained +later on: + +- `core-reporting` (tracing and logging utilities) +- `core-init` (new initialization framework) +- `core-testkit` (core-specific parts of driver-test-utils) + +The split was done in order to reduce coupling between components. Our +internal domain-model now only depends on `core-rest` and +`core-messaging`. Services can choose if and what other parts of core +to use, although it is recommended they use the `core-init` module. + +Although the split was mostly seemless, some cyclic dependencies +between packages were discovered. Since these are no longer possible +in a multi-project architecture, some source-breaking changes also had +to made. These will be listed later and marked as such. + +## New Features + +### Reporting Utilities + +A suite of tracing and logging utilities, based on the OpenTrace +specification, have been added as the `core-reporting` module. This +module plays a central role because it greatly simplifies debugging +distributed applications, and as such is depended upon by all other +modules. + +It embraces the "implicit context" pattern, and requires a +`SpanContext` for all logging actions. This context can be thought of +as a more type-safe "Message Diagnostic Context" and also replaces the +slf4j-based MDC utilities in previous versions of core. + +### Typeclass-based service discovery + +A type-class based service discovery has been added in +`xyz.driver.rest.DnsDiscovery`. It allows seemless instantiation of +client implementations that provide a "Service Descriptor" type +class. It also does not require any configuration (except for +overrides) and uses the DNS to resolve service hosts. + +### Initialization Framework + +A trait-based initialization framework has been added in +`core-init`. It implements common application startup procedures and +initializes common utilities for cloud-based web services. It +integrates all other core modules in an idiomatic way. + +## API-Breaking Changes + +Due to the structural changes mentioned previously, some API-breaking +changes were made: + +- `xyz.driver.rest.errors.DatabaseException` has been moved to + `xyz.driver.core.DatabaseException`, into the project + `core-types`. This move happened because both the `rest` and + `database` module require this exception. + +- `xyz.driver.core.FutureExtensions` has been moved to + `xyz.driver.core.rest` as it only contained logic that dealt with + service exceptions, something that belongs into `core-rest` and must + not be depended upon by `core-types`. + +- all classes but `AuthStub` in `xyz.driver.test` have been moved to + `xyz.driver.core.testkit` and are available in th + `core-testkit`. *(`AuthStub` has been moved to + `xyz.driver.users.testkit` in the project `users-testkit` in the + users service repo)* + +## Deprecation Notices + +Several major features have been deprecated and will be removed in the future. + +- `xyz.driver.core.app` will stop receiving updates as `xyz.driver.core.init` matures + +- `xyz.driver.core.time` and `xyz.driver.core.date` will be replaced + by their java 8 counterparts + +- the initial object-storage utilities in `xyz.driver.core.file` will + be removed in favor of the ones provided in `core-storage` + +- the first version of the message bus api `xyz.driver.core.pubsub` + will be replaced by `core-messaging` + +- `xyz.driver.driver.logging` will be removed as it is made obsolete + by `core-reporting` + +Deprecation of other classes in the [`core` support +module](src/main/scala/xyz/driver/core) is yet to be +determined. Please open an issue if you have any comments on them. + +# Version 1.x + +Please see git commit logs for changes prior to version 2. @@ -7,227 +7,119 @@ cherry-picked onto the [1.x branch](https://github.com/drivergroup/driver-core/tree/1.x). ---- +[![Build Status](https://travis-ci.com/drivergroup/driver-core.svg?token=S4oyfBY3YoEdLmckujJx&branch=master)](https://travis-ci.com/drivergroup/driver-core) -# Driver Core Library for Scala Services [![Build Status](https://travis-ci.com/drivergroup/driver-core.svg?token=S4oyfBY3YoEdLmckujJx&branch=master)](https://travis-ci.com/drivergroup/driver-core) +# Driver Core Library for Scala Services Multi-cloud utilities and application initialization framework. This library offers many common utilities for building applications -that run on multiple environments, including Google Cloud, Ali Cloud, +that run in multiple environments, including Google Cloud, Ali Cloud, and of course on development machines. +## Highlights -# Overview +- Cloud agnostic. -*This section applies to the 1.x series of core. The current development master branch may include changes not described here.* +- Sensible defaults. *Applications that use the initialization + framework can run out-of-the box on cloud or locally, with minimal + config required.* -Core library is used to provide ways to implement practices established in [Driver service template](http://github.com/drivergroup/driver-template) (check its [README.md](https://github.com/drivergroup/driver-template/blob/master/README.md)). +- Distributed tracing built into all major utilities. *Significant + actions are logged and reported automatically to an OpenTrace + aggregator.* -## Components +- Extensible. *Utilities are built to be used standalone. A + trait-based initialization framework provides utility instances for + common use-cases.* - * `core package` provides `Id` and `Name` implementations (with equal and ordering), utils for ScalaZ `OptionT`, and also `make` and `using` functions and `@@` (tagged) type, - * `tagging` Utilities for tagging primitive types for extra type safety, as well as some tags that involve extra transformation upon deserializing with spray, - * `config` Contains method `loadDefaultConfig` with default way of providing config to the application, - * `domain` Common generic domain objects, e.g., `Email` and `PhoneNumber`, - * `messages` Localization messages supporting different locales and methods to read from config, - * `database` Method for database initialization from config, `Id`, `Name`, `Time`, `Date` etc. mapping, schema lifecycle and base classes to implement and test `Repository` (data access objects), - * `rest` Wrapper over call to external REST API, authorization, context headers, XSS protection, does logging and allows to add Swagger to a service, - * `auth` Basic entities for authentication and authorization: `User`, `Role` `Permission` `AuthToken`, `AuthCredentials` etc., - * `swagger` Contains custom `AbstractModelConverter` to customize Swagger JSON with any Scala JSON formats created by, for instance, Spray JSON, - * `json` Json formats for `Id`, `Name`, `Time`, `Revision`, `Email`, `PhoneNumber`, `AuthCredentials` and converters for GADTs, enums and value classes, - * `file` Interface `FileStorage` and file storage implementations with GCS, S3 and local FS, - * `app` Base class for Driver service, which initializes swagger, app modules and its routes. - * `generators` Set of functions to prototype APIs. Combines with `faker` package, - * `stats` Methods to collect system stats: memory, cpu, gc, file system space usage, - * `logging` Custom Driver logging layout (not finished yet). +## Overview -Dependencies of core modules might be found in [Dependencies of the Modules diagram](https://github.com/drivergroup/driver-template/blob/master/Modules%20dependencies.pdf) file in driver-template repository in "Core component dependencies" section. +### Components -## Examples +This project is split into multiple submodules, each providing +specific functionality. -### Functions `make` and `using` -Those functions are especially useful to make procedural legacy Java APIs more functional and make scope of its usage more explicit. Runnable examples of its usage might be found in [`CoreTest`](https://github.com/drivergroup/driver-core/blob/master/src/test/scala/xyz/driver/core/CoreTest.scala), e.g., +Project | Description +-----------------|------------ +core | *(deprecated)* Previous application initialization framework. +core‑database | Utilities for working with databases, slick in particular. +core‑init | Mini-framework that offers default instances of many utilities, configured for the current platform. +core‑messaging | Library for abstracting over message buses such as Google PubSub. +core‑reporting | Combined tracing and logging library. +core‑rest | Abstractions to represent RESTful services, discovery and client implementations. +core‑storage | Object storage utilities. +core‑testkit | Mixins for scalatest. +core‑types | Type definitions that are commonly used by applications. +core‑util | Other utilities that do not belong anywhere else. **Note that this is a staging place for code that does not have its own submodule yet. Do not depend on it externally!** - useObject(make(new ObjectWithProceduralInitialization) { o => - o.setSetting1(...) // returns Unit - o.setSetting2(...) // returns Unit - o.setSetting3(...) // returns Unit - }) +These components and their internal dependencies can be represented +with the following graph. - // instead of - val o = new ObjectWithProceduralInitialization - o.setSetting1(...) - o.setSetting2(...) - o.setSetting3(...) +![Inter-Component Dependencies](documentation/components.svg) - useObject(o) +### Dependency -and - - using(... open output stream ...) { os => - os.write(...) - } - - // it will be close automatically - -### `OptionT` utils -Before - -``` -OptionT.optionT[Future](service.getRecords(id).map(Option.apply)) -OptionT.optionT(service.doSomething(id).map(_ => Option(()))) - -// Do not want to stop and return `None`, if `produceEffect` returns `None` -for { - x <- service.doSomething(id) - _ <- service.produceEffect(id, x).map(_ => ()).orElse(OptionT.some[Future, Unit](()))) -} yield x +All components are published together under the same version ([latest +version](https://github.com/drivergroup/driver-core/releases)). +```sbt +libraryDependencies += "xyz.driver" %% "core-<component>" % "<version>" ``` -after +### External dependencies -``` -service.getRecords(id).toOptionT -service.doSomething(id).toUnitOptionT - -// Do not want to stop and return `None` if `produceEffect` returns `None` -for { - x <- service.doSomething(id) - _ <- service.produceEffect(id, x).continueIgnoringNone -} yield x -``` +This project has quite a few external dependencies. See +[build.sbt](build.sbt) for a list of them. Some notable Scala +libraries used are: -### `@@` or Tagged types +- [akka-http](https://doc.akka.io/docs/akka-http/current/) +- [akka-stream](https://doc.akka.io/docs/akka/current/stream/) +- [enumeratum](https://github.com/lloydmeta/enumeratum#enumeratum------) +- [scala-async](https://github.com/scala/scala-async) +- [slick](http://slick.lightbend.com/) +- [spray-json](https://github.com/spray/spray-json) +- [sttp](https://github.com/softwaremill/sttp) -For type definitions, the only import required is +## Example Usage +The following implements a simple key-value store that uses object +storage ("buckets") when running in the cloud and a local filesystem +when running on a development machine. ```scala -import xyz.driver.core.@@ -``` - -which provides just the ability to tag types: `val value: String @@ Important`. Two `String`s with different tags will -be distinguished by the compiler, helping reduce the possibility of mixing values passed into methods with several -arguments of identical types. - -To work with tags in actual values, use the following convenience methods: - -```scala -import xyz.driver.core.tagging._ - -val str = "abc".tagged[Important] -``` - -or go back to plain (say, in case you have an implicit for untagged value) - -```scala -// val trimmedExternalId: String @@ Trimmed = "123" - -Trials.filter(_.externalId === trimmedExternalId.untag) -``` - -### `Time` and `TimeProvider` - -**NOTE: The contents of this section has been deprecated - use java.time.Clock instead** - -Usage examples for `Time` (also check [TimeTest](https://github.com/drivergroup/driver-core/blob/master/src/test/scala/xyz/driver/core/TimeTest.scala) for more examples). - - Time(234L).isAfter(Time(123L)) - - Time(123L).isBefore(Time(234L)) - - Time(123L).advanceBy(4 days) - - Seq(Time(321L), Time(123L), Time(231L)).sorted - - startOfMonth(Time(1468937089834L)) should be (Time(1467381889834L)) - - textualDate(Time(1468937089834L)) should be ("July 19, 2016") +import xyz.driver.core.init - textualTime(Time(1468937089834L)) should be ("Jul 19, 2016 10:04:49 AM") +object Main extends init.SimpleHttpApp { - TimeRange(Time(321L), Time(432L)).duration should be(111.milliseconds) + lazy val fs = storage("data") - -### Generators -Example of how to generate a case class instance, - - import com.drivergrp.core._ - - Consumer( - generators.nextId[Consumer], - Name[Consumer](faker.Name.name), - faker.Lorem.sentence(word_count = 10)) - - -For more examples check [project tests](https://github.com/drivergroup/driver-core/tree/master/src/test/scala/xyz/driver/core) or [service template](http://github.com/drivergroup/driver-template) repository. - -### App - -To start a new application using standard Driver application class, follow this pattern: - - object MyApp extends App { - - new DriverApp(BuildInfo.version, - BuildInfo.gitHeadCommit.getOrElse("None"), - modules = Seq(myModule1, myModule2), - time, log, config, - interface = "::0", baseUrl, scheme, port) - (servicesActorSystem, servicesExecutionContext).run() - } - -### REST -With REST utils, for instance, you can use the following directives in [akka-http](https://github.com/akka/akka-http) routes, as follows - - sanitizeRequestEntity { // Prevents XSS - serviceContext { implicit ctx => // Extracts context headers from request - authorize(CanSeeUser(userId)) { user => // Authorizes and extracts user - // Your code using `ctx` and `user` - } + override def applicationRoute = path("data" / Segment) { key => + post { + entity(as[Array[Byte]]) { value => + complete(fs.uploadContent(key, value)) + } + } ~ get { + rejectEmptyResponse{ + complete(fs.content(key)) } } + } -### Swagger -Swagger JSON formats built using reflection can be overriden by using `CustomSwaggerJsonConverter` at the start of your application initialization in the following way: - - ModelConverters.getInstance() - .addConverter(new CustomSwaggerJsonConverter(Json.mapper(), - CustomSwaggerFormats.customProperties, CustomSwaggerFormats.customObjectsExamples)) - -### Locale -Locale messages can be initialized and used in the following way, - - val localeConfig = config.getConfig("locale") - val log = com.typesafe.scalalogging.Logger(LoggerFactory.getLogger(classOf[MyClass])) - - val messages = xyz.driver.core.messages.Messages.messages(localeConfig, log, Locale.US) - - messages("my.locale.message") - messages("my.locale.message.with.param", parameter) - - -### System stats -Stats it gives access to are, - - xyz.driver.core.stats.SystemStats.memoryUsage - - xyz.driver.core.stats.SystemStats.availableProcessors - - xyz.driver.core.stats.SystemStats.garbageCollectorStats - - xyz.driver.core.stats.SystemStats.fileSystemSpace - - xyz.driver.core.stats.SystemStats.operatingSystemStats - - -## Running - -1. Compile everything and test: +} +``` +See [this example project](documentation/example) for a more details. - $ sbt test +## Building -2. Publish to local repository, to use changes in depending projects: +This project uses [sbt](https://www.scala-sbt.org/) as build tool. It +has grown organically over time, but all new code should follow the +best practices outlined +[here](https://style.driver.engineering/scala.html). - $ sbt publishLocal +It is set up to automatically build, test, and publish a release when +a Git tag is pushed that matches the regular expression +`v[0-9].*`. Hence, to publish a new version "1.2.3", simply create a +tag "v1.2.3" and `git push --tags` to origin. -3. In order to release a new version of core, merge your PR, tag the HEAD master commit with the next version - (don't forget the "v." prefix) and push tags - Travis will release the artifact automatically! +## Copying +Copyright 2016-2018 Driver Inc. Released under the Apache License, +Version 2.0. @@ -1,56 +1,125 @@ import sbt._ import Keys._ -lazy val core = project - .in(file(".")) +scalacOptions in ThisBuild in (Compile, doc) ++= Seq( + "-groups", // group similar methods together based on the @group annotation. + "-diagrams", // show class hierarchy diagrams (requires 'dot' to be available on path) + "-implicits", // add methods "inherited" through implicit conversions + "-sourcepath", + baseDirectory.value.getAbsolutePath, + "-doc-source-url", + s"https://github.com/drivergroup/driver-core/blob/master€{FILE_PATH}.scala" +) +abiVersion in ThisBuild := "2.0.0-M3" + +val mockito = "org.mockito" % "mockito-core" % "1.9.5" +val scalatest = "org.scalatest" %% "scalatest" % "3.0.5" + +// TODO these shouldn't be declared in the build scope. They should be moved to the individual +// sub-projects that actually depend on them. +libraryDependencies in ThisBuild ++= Seq( + // please keep these sorted alphabetically + "ch.qos.logback" % "logback-classic" % "1.2.3", + "ch.qos.logback.contrib" % "logback-jackson" % "0.1.5", + "ch.qos.logback.contrib" % "logback-json-classic" % "0.1.5", + "com.aliyun.mns" % "aliyun-sdk-mns" % "1.1.8", + "com.aliyun.oss" % "aliyun-sdk-oss" % "2.8.2", + "com.amazonaws" % "aws-java-sdk-s3" % "1.11.342", + "com.beachape" %% "enumeratum" % "1.5.13", + "com.github.swagger-akka-http" %% "swagger-akka-http" % "1.0.0", + "com.google.cloud" % "google-cloud-pubsub" % "1.31.0", + "com.google.cloud" % "google-cloud-storage" % "1.31.0", + "com.googlecode.libphonenumber" % "libphonenumber" % "8.9.7", + "com.neovisionaries" % "nv-i18n" % "1.23", + "com.pauldijou" %% "jwt-core" % "0.16.0", + "com.softwaremill.sttp" %% "akka-http-backend" % "1.2.2", + "com.softwaremill.sttp" %% "core" % "1.2.2", + "com.typesafe" % "config" % "1.3.3", + "com.typesafe.akka" %% "akka-actor" % "2.5.14", + "com.typesafe.akka" %% "akka-http-core" % "10.1.4", + "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.4", + "com.typesafe.akka" %% "akka-http-testkit" % "10.1.4", + "com.typesafe.akka" %% "akka-stream" % "2.5.14", + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.0", + "com.typesafe.slick" %% "slick" % "3.2.3", + "eu.timepit" %% "refined" % "0.9.0", + "io.kamon" %% "kamon-akka-2.5" % "1.0.0", + "io.kamon" %% "kamon-core" % "1.1.3", + "io.kamon" %% "kamon-statsd" % "1.0.0", + "io.kamon" %% "kamon-system-metrics" % "1.0.0", + "javax.xml.bind" % "jaxb-api" % "2.2.8", + mockito % "test", + "org.scala-lang.modules" %% "scala-async" % "0.9.7", + "org.scalacheck" %% "scalacheck" % "1.14.0" % "test", + "org.scalaz" %% "scalaz-core" % "7.2.24", + scalatest % "test", + "xyz.driver" %% "spray-json-derivation" % "0.6.0", + "xyz.driver" %% "tracing" % "0.1.2" +) + +lazy val `core-util` = project + .enablePlugins(LibraryPlugin) + +lazy val `core-types` = project .enablePlugins(LibraryPlugin) + .dependsOn(`core-util`) + +lazy val `core-rest` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-util`, `core-types`, `core-reporting`) + +lazy val `core-reporting` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-util`) + +lazy val `core-storage` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-reporting`) + +lazy val `core-messaging` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-reporting`) + +lazy val `core-database` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-types`) + +lazy val `core-init` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-reporting`, `core-storage`, `core-messaging`, `core-rest`, `core-database`) + +lazy val `core-testkit` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-rest`, `core-database`) .settings( libraryDependencies ++= Seq( - // please keep these sorted alphabetically - "ch.qos.logback" % "logback-classic" % "1.2.3", - "ch.qos.logback.contrib" % "logback-jackson" % "0.1.5", - "ch.qos.logback.contrib" % "logback-json-classic" % "0.1.5", - "com.aliyun.mns" % "aliyun-sdk-mns" % "1.1.8", - "com.aliyun.oss" % "aliyun-sdk-oss" % "2.8.2", - "com.amazonaws" % "aws-java-sdk-s3" % "1.11.342", - "com.beachape" %% "enumeratum" % "1.5.13", - "com.github.swagger-akka-http" %% "swagger-akka-http" % "1.0.0", - "com.google.cloud" % "google-cloud-pubsub" % "1.31.0", - "com.google.cloud" % "google-cloud-storage" % "1.31.0", - "com.googlecode.libphonenumber" % "libphonenumber" % "8.9.7", - "com.neovisionaries" % "nv-i18n" % "1.23", - "com.pauldijou" %% "jwt-core" % "0.16.0", - "com.softwaremill.sttp" %% "akka-http-backend" % "1.2.2", - "com.softwaremill.sttp" %% "core" % "1.2.2", - "com.typesafe" % "config" % "1.3.3", - "com.typesafe.akka" %% "akka-actor" % "2.5.14", - "com.typesafe.akka" %% "akka-http-core" % "10.1.4", - "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.4", - "com.typesafe.akka" %% "akka-http-testkit" % "10.1.4", - "com.typesafe.akka" %% "akka-stream" % "2.5.14", - "com.typesafe.scala-logging" %% "scala-logging" % "3.9.0", - "com.typesafe.slick" %% "slick" % "3.2.3", - "eu.timepit" %% "refined" % "0.9.0", - "io.kamon" %% "kamon-akka-2.5" % "1.0.0", - "io.kamon" %% "kamon-core" % "1.1.3", - "io.kamon" %% "kamon-statsd" % "1.0.0", - "io.kamon" %% "kamon-system-metrics" % "1.0.0", - "javax.xml.bind" % "jaxb-api" % "2.2.8", - "org.mockito" % "mockito-core" % "1.9.5" % "test", - "org.scala-lang.modules" %% "scala-async" % "0.9.7", - "org.scalacheck" %% "scalacheck" % "1.14.0" % "test", - "org.scalatest" %% "scalatest" % "3.0.5" % "test", - "org.scalaz" %% "scalaz-core" % "7.2.24", - "xyz.driver" %% "spray-json-derivation" % "0.6.0", - "xyz.driver" %% "tracing" % "0.1.2" - ), - scalacOptions in (Compile, doc) ++= Seq( - "-groups", // group similar methods together based on the @group annotation. - "-diagrams", // show classs hierarchy diagrams (requires 'dot' to be available on path) - "-implicits", // add methods "inherited" through implicit conversions - "-sourcepath", - baseDirectory.value.getAbsolutePath, - "-doc-source-url", - s"https://github.com/drivergroup/driver-core/blob/master€{FILE_PATH}.scala" + "com.spotify" % "docker-client" % "8.11.7" classifier "shaded", + "org.postgresql" % "postgresql" % "9.4.1212", + "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0", + scalatest ) ) + +lazy val core = project + .in(file(".")) + .enablePlugins(LibraryPlugin) + .dependsOn( + `core-types`, + `core-rest`, + `core-reporting`, + `core-storage`, + `core-messaging`, + `core-database`, + `core-init` + ) + .aggregate( + `core-testkit`, + `core-types`, + `core-rest`, + `core-reporting`, + `core-storage`, + `core-messaging`, + `core-database`, + `core-init`, + `core-util` + ) diff --git a/src/main/scala/xyz/driver/core/database/Converters.scala b/core-database/src/main/scala/xyz/driver/core/database/Converters.scala index ad79abf..26b6eea 100644 --- a/src/main/scala/xyz/driver/core/database/Converters.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/Converters.scala @@ -1,6 +1,6 @@ package xyz.driver.core.database -import xyz.driver.core.rest.errors.DatabaseException +import xyz.driver.core.DatabaseException import scala.reflect.ClassTag diff --git a/src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala b/core-database/src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala index e2efd32..e2efd32 100644 --- a/src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/PatchedHsqldbProfile.scala diff --git a/src/main/scala/xyz/driver/core/database/Repository.scala b/core-database/src/main/scala/xyz/driver/core/database/Repository.scala index 5d7f787..5d7f787 100644 --- a/src/main/scala/xyz/driver/core/database/Repository.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/Repository.scala diff --git a/src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala b/core-database/src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala index 8293371..8293371 100644 --- a/src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/SlickGetResultSupport.scala diff --git a/src/main/scala/xyz/driver/core/database/database.scala b/core-database/src/main/scala/xyz/driver/core/database/database.scala index f3630ff..f3630ff 100644 --- a/src/main/scala/xyz/driver/core/database/database.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/database.scala diff --git a/src/main/scala/xyz/driver/core/database/package.scala b/core-database/src/main/scala/xyz/driver/core/database/package.scala index aee14c6..aee14c6 100644 --- a/src/main/scala/xyz/driver/core/database/package.scala +++ b/core-database/src/main/scala/xyz/driver/core/database/package.scala diff --git a/src/test/scala/xyz/driver/core/database/DatabaseTest.scala b/core-database/src/test/scala/xyz/driver/core/database/DatabaseTest.scala index 8d2a4ac..3407d64 100644 --- a/src/test/scala/xyz/driver/core/database/DatabaseTest.scala +++ b/core-database/src/test/scala/xyz/driver/core/database/DatabaseTest.scala @@ -2,7 +2,7 @@ package xyz.driver.core.database import org.scalatest.{FlatSpec, Matchers} import org.scalatest.prop.Checkers -import xyz.driver.core.rest.errors.DatabaseException +import xyz.driver.core.DatabaseException class DatabaseTest extends FlatSpec with Matchers with Checkers { import xyz.driver.core.generators._ diff --git a/src/main/resources/deployed-logback.xml b/core-init/src/main/resources/deployed-logback.xml index b626b4b..b626b4b 100644 --- a/src/main/resources/deployed-logback.xml +++ b/core-init/src/main/resources/deployed-logback.xml diff --git a/src/main/resources/logback-test.xml b/core-init/src/main/resources/logback-test.xml index d1a17ef..d1a17ef 100644 --- a/src/main/resources/logback-test.xml +++ b/core-init/src/main/resources/logback-test.xml diff --git a/src/main/resources/logback.xml b/core-init/src/main/resources/logback.xml index 97baf6d..97baf6d 100644 --- a/src/main/resources/logback.xml +++ b/core-init/src/main/resources/logback.xml diff --git a/core-init/src/main/resources/reference.conf b/core-init/src/main/resources/reference.conf new file mode 100644 index 0000000..d5c7c4b --- /dev/null +++ b/core-init/src/main/resources/reference.conf @@ -0,0 +1,26 @@ +###################################################################### +# Default settings for driver core. Any settings defined by users of # +# this library will take precedence. See the documentation of the # +# Typesafe Config Library (https://github.com/lightbend/config) for # +# more information. # +###################################################################### + +# Kamon provides monitoring capabilities +kamon { + system-metrics { + # sigar reports host-specific metrics. Kubernetes takes care of + # that for Driver services. + host.enabled = false + + # JVM-related metrics + jmx.enabled = true + } + + statsd { + hostname = localhost + port = 8125 + simple-metric-key-generator { + include-hostname = false + } + } +} diff --git a/src/main/scala/xyz/driver/core/init/AkkaBootable.scala b/core-init/src/main/scala/xyz/driver/core/init/AkkaBootable.scala index df6611e..b012bf9 100644 --- a/src/main/scala/xyz/driver/core/init/AkkaBootable.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/AkkaBootable.scala @@ -179,7 +179,7 @@ trait AkkaBootable { syslog("binding to network interface") val binding = Await.result( - Http().bindAndHandle(route, "::", port), + Http().bindAndHandle(route, "0.0.0.0", port), 2.seconds ) syslog(s"listening to ${binding.localAddress}") diff --git a/src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala b/core-init/src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala index 0e53085..a8bee9b 100644 --- a/src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/BuildInfoReflection.scala @@ -10,7 +10,7 @@ private[init] object BuildInfoReflection { final val BuildInfoName = "xyz.driver.BuildInfo" - lazy val name: String = get[String]("name") + lazy val name: String = find[String]("name").getOrElse("unknown") lazy val version: Option[String] = find[String]("version") /** Lookup a given field in the build configuration. This field is required to exist. */ diff --git a/src/main/scala/xyz/driver/core/init/CloudServices.scala b/core-init/src/main/scala/xyz/driver/core/init/CloudServices.scala index 857dd4c..8c51e38 100644 --- a/src/main/scala/xyz/driver/core/init/CloudServices.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/CloudServices.scala @@ -3,10 +3,10 @@ package init import java.nio.file.Paths -import xyz.driver.core.messaging.{CreateOnDemand, GoogleBus, QueueBus, StreamBus} +import xyz.driver.core.messaging.{AliyunBus, CreateOnDemand, GoogleBus, QueueBus, StreamBus} import xyz.driver.core.reporting._ import xyz.driver.core.rest.DnsDiscovery -import xyz.driver.core.storage.{BlobStorage, FileSystemBlobStorage, GcsBlobStorage} +import xyz.driver.core.storage.{AliyunBlobStorage, BlobStorage, FileSystemBlobStorage, GcsBlobStorage} import scala.collection.JavaConverters._ @@ -52,6 +52,10 @@ trait CloudServices extends AkkaBootable { self => new GoogleReporter(p.credentials, p.namespace) with ScalaLoggingCompat with GoogleMdcLogger { val logger = ScalaLoggingCompat.defaultScalaLogger(true) } + case _: Platform.AliCloud => + new NoTraceReporter with ScalaLoggingCompat { + val logger = ScalaLoggingCompat.defaultScalaLogger(true) + } case Platform.Dev => new NoTraceReporter with ScalaLoggingCompat { val logger = ScalaLoggingCompat.defaultScalaLogger(false) @@ -64,7 +68,7 @@ trait CloudServices extends AkkaBootable { self => /** Object storage. * * When running on a cloud platform, prepends `$project-` to bucket names, where `$project` - * is the project ID (for example 'driverinc-production` or `driverinc-sandbox`). + * is the project ID (for example `driverinc-production` or `driverinc-sandbox`). * * @group utilities */ @@ -72,6 +76,8 @@ trait CloudServices extends AkkaBootable { self => platform match { case p @ Platform.GoogleCloud(keyfile, _) => GcsBlobStorage.fromKeyfile(keyfile, s"${p.project}-$bucketName") + case Platform.AliCloud(project, _, accessId, accessKey, region, _) => + AliyunBlobStorage(accessId, accessKey, region, s"$project-$bucketName", java.time.Clock.systemDefaultZone()) case Platform.Dev => new FileSystemBlobStorage(Paths.get(s".data-$bucketName")) } @@ -82,6 +88,8 @@ trait CloudServices extends AkkaBootable { self => def messageBus: StreamBus = platform match { case p @ Platform.GoogleCloud(_, namespace) => new GoogleBus(p.credentials, namespace) with StreamBus with CreateOnDemand + case Platform.AliCloud(_, accountId, accessId, accessKey, region, namespace) => + new AliyunBus(accountId, accessId, accessKey, region, namespace) with StreamBus with CreateOnDemand case Platform.Dev => new QueueBus()(self.system) with StreamBus } diff --git a/src/main/scala/xyz/driver/core/init/HttpApi.scala b/core-init/src/main/scala/xyz/driver/core/init/HttpApi.scala index 81428bf..2570cb3 100644 --- a/src/main/scala/xyz/driver/core/init/HttpApi.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/HttpApi.scala @@ -27,7 +27,7 @@ trait HttpApi extends CloudServices with Directives with SprayJsonSupport { self /** Classes with Swagger annotations. * @group hooks */ - def swaggerRouteClasses: Set[Class[_]] + def swaggerRouteClasses: Set[Class[_]] = Set(self.getClass) private val healthRoute = path("health") { complete(Map("status" -> "good").toJson) diff --git a/core-init/src/main/scala/xyz/driver/core/init/Platform.scala b/core-init/src/main/scala/xyz/driver/core/init/Platform.scala new file mode 100644 index 0000000..8f8ef83 --- /dev/null +++ b/core-init/src/main/scala/xyz/driver/core/init/Platform.scala @@ -0,0 +1,68 @@ +package xyz.driver.core +package init + +import java.nio.file.{Files, Path, Paths} + +import com.google.auth.oauth2.ServiceAccountCredentials + +sealed trait Platform +object Platform { + case class GoogleCloud(keyfile: Path, namespace: String) extends Platform { + def credentials: ServiceAccountCredentials = ServiceAccountCredentials.fromStream( + Files.newInputStream(keyfile) + ) + def project: String = credentials.getProjectId + } + + object GoogleCloud { + lazy val fromEnv: GoogleCloud = { + val credentialsFile = + sys.env.getOrElse( + "GOOGLE_APPLICATION_CREDENTIALS", + sys.error("Expected GOOGLE_APPLICATION_CREDENTIALS file to be set in gcp environment")) + + val keyfile = Paths.get(credentialsFile) + require(Files.isReadable(keyfile), s"Google credentials file $credentialsFile is not readable.") + + val namespace = sys.env.getOrElse("SERVICE_NAMESPACE", sys.error("Namespace not set")) + GoogleCloud(keyfile, namespace) + } + } + + case class AliCloud( + project: String, + accountId: String, + accessId: String, + accessKey: String, + region: String, + namespace: String) + extends Platform + + object AliCloud { + lazy val fromEnv: AliCloud = { + AliCloud( + project = sys.env("CLOUD_PROJECT"), + accountId = sys.env("ALICLOUD_ACCOUNT_ID"), + accessId = sys.env("ALICLOUD_ACCESS_ID"), + accessKey = sys.env("ALICLOUD_ACCESS_KEY"), + region = sys.env("CLOUD_REGION"), + namespace = sys.env("SERVICE_NAMESPACE") + ) + } + } + + case object Dev extends Platform + + lazy val fromEnv: Platform = { + sys.env.get("CLOUD_PROVIDER") match { + case Some("alicloud") => AliCloud.fromEnv + case Some("gcp") => GoogleCloud.fromEnv + + // For backwards compat, try instantiating GCP first, falling back to Dev + case _ => util.Try(GoogleCloud.fromEnv).getOrElse(Dev) + } + } + + def current: Platform = fromEnv + +} diff --git a/src/main/scala/xyz/driver/core/init/ProtobufApi.scala b/core-init/src/main/scala/xyz/driver/core/init/ProtobufApi.scala index 284ac67..284ac67 100644 --- a/src/main/scala/xyz/driver/core/init/ProtobufApi.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/ProtobufApi.scala diff --git a/src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala b/core-init/src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala index 61ca363..61ca363 100644 --- a/src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala +++ b/core-init/src/main/scala/xyz/driver/core/init/SimpleHttpApp.scala diff --git a/src/main/scala/xyz/driver/core/messaging/AliyunBus.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/AliyunBus.scala index c23ea0f..063bee3 100644 --- a/src/main/scala/xyz/driver/core/messaging/AliyunBus.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/AliyunBus.scala @@ -16,7 +16,7 @@ class AliyunBus( accessSecret: String, region: String, namespace: String, - pullTimeout: Int + pullTimeout: Int = 30 )(implicit val executionContext: ExecutionContext) extends Bus { private val endpoint = s"https://$accountId.mns.$region.aliyuncs.com" @@ -136,18 +136,22 @@ class AliyunBus( def createSubscription(topic: Topic[_], config: SubscriptionConfig): Future[Unit] = Future { val subscriptionName = rawSubscriptionName(config, topic) - val topicName = rawTopicName(topic) - val topicRef = client.getTopicRef(topicName) - - val queueMeta = new QueueMeta - queueMeta.setQueueName(subscriptionName) - queueMeta.setVisibilityTimeout(config.ackTimeout.toSeconds) - client.createQueue(queueMeta) - - val subscriptionMeta = new SubscriptionMeta - subscriptionMeta.setSubscriptionName(subscriptionName) - subscriptionMeta.setTopicName(topicName) - subscriptionMeta.setEndpoint(topicRef.generateQueueEndpoint(subscriptionName)) - topicRef.subscribe(subscriptionMeta) + val queueExists = Option(client.listQueue(subscriptionName, "", 1)).exists(!_.getResult.isEmpty) + + if (!queueExists) { + val topicName = rawTopicName(topic) + val topicRef = client.getTopicRef(topicName) + + val queueMeta = new QueueMeta + queueMeta.setQueueName(subscriptionName) + queueMeta.setVisibilityTimeout(config.ackTimeout.toSeconds) + client.createQueue(queueMeta) + + val subscriptionMeta = new SubscriptionMeta + subscriptionMeta.setSubscriptionName(subscriptionName) + subscriptionMeta.setTopicName(topicName) + subscriptionMeta.setEndpoint(topicRef.generateQueueEndpoint(subscriptionName)) + topicRef.subscribe(subscriptionMeta) + } } } diff --git a/src/main/scala/xyz/driver/core/messaging/Bus.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/Bus.scala index 75954f4..75954f4 100644 --- a/src/main/scala/xyz/driver/core/messaging/Bus.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/Bus.scala diff --git a/src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala index 1af5308..1af5308 100644 --- a/src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/CreateOnDemand.scala diff --git a/src/main/scala/xyz/driver/core/messaging/GoogleBus.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/GoogleBus.scala index b296c50..b296c50 100644 --- a/src/main/scala/xyz/driver/core/messaging/GoogleBus.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/GoogleBus.scala diff --git a/src/main/scala/xyz/driver/core/messaging/QueueBus.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/QueueBus.scala index 45c9ed5..45c9ed5 100644 --- a/src/main/scala/xyz/driver/core/messaging/QueueBus.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/QueueBus.scala diff --git a/src/main/scala/xyz/driver/core/messaging/StreamBus.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/StreamBus.scala index a9ba3a7..44d75cd 100644 --- a/src/main/scala/xyz/driver/core/messaging/StreamBus.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/StreamBus.scala @@ -76,7 +76,7 @@ trait StreamBus extends Bus { maxRestarts ) { () => subscribe(topic, config) - .via(processMessage.recover({ case _ => Nil })) + .via(processMessage) .log(topic.name) .mapConcat(identity) } diff --git a/src/main/scala/xyz/driver/core/messaging/Topic.scala b/core-messaging/src/main/scala/xyz/driver/core/messaging/Topic.scala index 32fd764..32fd764 100644 --- a/src/main/scala/xyz/driver/core/messaging/Topic.scala +++ b/core-messaging/src/main/scala/xyz/driver/core/messaging/Topic.scala diff --git a/src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala b/core-messaging/src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala index 8dd0776..8dd0776 100644 --- a/src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala +++ b/core-messaging/src/test/scala/xyz/driver/core/messaging/QueueBusTest.scala diff --git a/src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala index f5c41cf..f5c41cf 100644 --- a/src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleMdcLogger.scala diff --git a/src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala index 14c4954..14c4954 100644 --- a/src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/GoogleReporter.scala diff --git a/src/main/scala/xyz/driver/core/reporting/NoReporter.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/NoReporter.scala index c1c81f4..c1c81f4 100644 --- a/src/main/scala/xyz/driver/core/reporting/NoReporter.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/NoReporter.scala diff --git a/src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala index b49cfda..b49cfda 100644 --- a/src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/NoTraceReporter.scala diff --git a/src/main/scala/xyz/driver/core/reporting/Reporter.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/Reporter.scala index 469084c..469084c 100644 --- a/src/main/scala/xyz/driver/core/reporting/Reporter.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/Reporter.scala diff --git a/src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala index 0ff5574..0ff5574 100644 --- a/src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/ScalaLoggingCompat.scala diff --git a/src/main/scala/xyz/driver/core/reporting/SpanContext.scala b/core-reporting/src/main/scala/xyz/driver/core/reporting/SpanContext.scala index 04a822d..04a822d 100644 --- a/src/main/scala/xyz/driver/core/reporting/SpanContext.scala +++ b/core-reporting/src/main/scala/xyz/driver/core/reporting/SpanContext.scala diff --git a/src/main/resources/reference.conf b/core-rest/src/main/resources/reference.conf index 0c3b4e2..ea56cb5 100644 --- a/src/main/resources/reference.conf +++ b/core-rest/src/main/resources/reference.conf @@ -1,10 +1,3 @@ -###################################################################### -# Default settings for driver core. Any settings defined by users of # -# this library will take precedence. See the documentation of the # -# Typesafe Config Library (https://github.com/lightbend/config) for # -# more information. # -###################################################################### - # This scope is for general settings related to the execution of a # specific service. application { @@ -59,23 +52,3 @@ swagger { licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0" } } - -# Kamon provides monitoring capabilities -kamon { - system-metrics { - # sigar reports host-specific metrics. Kubernetes takes care of - # that for Driver services. - host.enabled = false - - # JVM-related metrics - jmx.enabled = true - } - - statsd { - hostname = localhost - port = 8125 - simple-metric-key-generator { - include-hostname = false - } - } -} diff --git a/src/main/resources/swagger-ui-dist/README.md b/core-rest/src/main/resources/swagger-ui-dist/README.md index 6628422..6628422 100644 --- a/src/main/resources/swagger-ui-dist/README.md +++ b/core-rest/src/main/resources/swagger-ui-dist/README.md diff --git a/src/main/resources/swagger-ui-dist/absolute-path.js b/core-rest/src/main/resources/swagger-ui-dist/absolute-path.js index af42bc8..af42bc8 100644 --- a/src/main/resources/swagger-ui-dist/absolute-path.js +++ b/core-rest/src/main/resources/swagger-ui-dist/absolute-path.js diff --git a/src/main/resources/swagger-ui-dist/favicon-16x16.png b/core-rest/src/main/resources/swagger-ui-dist/favicon-16x16.png Binary files differindex 0f7e13b..0f7e13b 100644 --- a/src/main/resources/swagger-ui-dist/favicon-16x16.png +++ b/core-rest/src/main/resources/swagger-ui-dist/favicon-16x16.png diff --git a/src/main/resources/swagger-ui-dist/favicon-32x32.png b/core-rest/src/main/resources/swagger-ui-dist/favicon-32x32.png Binary files differindex b0a3352..b0a3352 100644 --- a/src/main/resources/swagger-ui-dist/favicon-32x32.png +++ b/core-rest/src/main/resources/swagger-ui-dist/favicon-32x32.png diff --git a/src/main/resources/swagger-ui-dist/index.html b/core-rest/src/main/resources/swagger-ui-dist/index.html index b7385c0..b7385c0 100644 --- a/src/main/resources/swagger-ui-dist/index.html +++ b/core-rest/src/main/resources/swagger-ui-dist/index.html diff --git a/src/main/resources/swagger-ui-dist/index.js b/core-rest/src/main/resources/swagger-ui-dist/index.js index c229ec4..c229ec4 100644 --- a/src/main/resources/swagger-ui-dist/index.js +++ b/core-rest/src/main/resources/swagger-ui-dist/index.js diff --git a/src/main/resources/swagger-ui-dist/oauth2-redirect.html b/core-rest/src/main/resources/swagger-ui-dist/oauth2-redirect.html index fb68399..fb68399 100644 --- a/src/main/resources/swagger-ui-dist/oauth2-redirect.html +++ b/core-rest/src/main/resources/swagger-ui-dist/oauth2-redirect.html diff --git a/src/main/resources/swagger-ui-dist/package.json b/core-rest/src/main/resources/swagger-ui-dist/package.json index 1e74e17..1e74e17 100644 --- a/src/main/resources/swagger-ui-dist/package.json +++ b/core-rest/src/main/resources/swagger-ui-dist/package.json diff --git a/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js index 8c5384f..8c5384f 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js diff --git a/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map index f3d7114..f3d7114 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-bundle.js.map diff --git a/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js index 7fa58dd..7fa58dd 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js diff --git a/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map index ac39601..ac39601 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui-standalone-preset.js.map diff --git a/src/main/resources/swagger-ui-dist/swagger-ui.css b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css index f6d75f4..f6d75f4 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui.css +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css diff --git a/src/main/resources/swagger-ui-dist/swagger-ui.css.map b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css.map index 3e2402c..3e2402c 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui.css.map +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.css.map diff --git a/src/main/resources/swagger-ui-dist/swagger-ui.js b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js index ac23356..ac23356 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui.js +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js diff --git a/src/main/resources/swagger-ui-dist/swagger-ui.js.map b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js.map index f2393d8..f2393d8 100644 --- a/src/main/resources/swagger-ui-dist/swagger-ui.js.map +++ b/core-rest/src/main/resources/swagger-ui-dist/swagger-ui.js.map diff --git a/src/main/resources/swagger-ui/.gitattributes b/core-rest/src/main/resources/swagger-ui/.gitattributes index 2bf4397..2bf4397 100644 --- a/src/main/resources/swagger-ui/.gitattributes +++ b/core-rest/src/main/resources/swagger-ui/.gitattributes diff --git a/src/main/resources/swagger-ui/css/print.css b/core-rest/src/main/resources/swagger-ui/css/print.css index 8695cf5..8695cf5 100644 --- a/src/main/resources/swagger-ui/css/print.css +++ b/core-rest/src/main/resources/swagger-ui/css/print.css diff --git a/src/main/resources/swagger-ui/css/reset.css b/core-rest/src/main/resources/swagger-ui/css/reset.css index b2b0789..b2b0789 100644 --- a/src/main/resources/swagger-ui/css/reset.css +++ b/core-rest/src/main/resources/swagger-ui/css/reset.css diff --git a/src/main/resources/swagger-ui/css/screen.css b/core-rest/src/main/resources/swagger-ui/css/screen.css index 10a46ac..10a46ac 100644 --- a/src/main/resources/swagger-ui/css/screen.css +++ b/core-rest/src/main/resources/swagger-ui/css/screen.css diff --git a/src/main/resources/swagger-ui/css/typography.css b/core-rest/src/main/resources/swagger-ui/css/typography.css index 27c3751..27c3751 100644 --- a/src/main/resources/swagger-ui/css/typography.css +++ b/core-rest/src/main/resources/swagger-ui/css/typography.css diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot Binary files differindex d852498..d852498 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.eot diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg index a54bbbb..a54bbbb 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.svg diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf Binary files differindex 15896c4..15896c4 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.ttf diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff Binary files differindex 67e3e25..67e3e25 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 Binary files differindex 1e726a7..1e726a7 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot Binary files differindex ac2698e..ac2698e 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.eot diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg index d9f2a21..d9f2a21 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.svg diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf Binary files differindex fb8cea6..fb8cea6 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff Binary files differindex abf1989..abf1989 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff diff --git a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 Binary files differindex 9f93f74..9f93f74 100644 --- a/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 +++ b/core-rest/src/main/resources/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 diff --git a/src/main/resources/swagger-ui/images/explorer_icons.png b/core-rest/src/main/resources/swagger-ui/images/explorer_icons.png Binary files differindex ed9d2ff..ed9d2ff 100644 --- a/src/main/resources/swagger-ui/images/explorer_icons.png +++ b/core-rest/src/main/resources/swagger-ui/images/explorer_icons.png diff --git a/src/main/resources/swagger-ui/images/favicon-16x16.png b/core-rest/src/main/resources/swagger-ui/images/favicon-16x16.png Binary files differindex 66b1a5b..66b1a5b 100644 --- a/src/main/resources/swagger-ui/images/favicon-16x16.png +++ b/core-rest/src/main/resources/swagger-ui/images/favicon-16x16.png diff --git a/src/main/resources/swagger-ui/images/favicon-32x32.png b/core-rest/src/main/resources/swagger-ui/images/favicon-32x32.png Binary files differindex 32f319f..32f319f 100644 --- a/src/main/resources/swagger-ui/images/favicon-32x32.png +++ b/core-rest/src/main/resources/swagger-ui/images/favicon-32x32.png diff --git a/src/main/resources/swagger-ui/images/favicon.ico b/core-rest/src/main/resources/swagger-ui/images/favicon.ico Binary files differindex 8b60bcf..8b60bcf 100644 --- a/src/main/resources/swagger-ui/images/favicon.ico +++ b/core-rest/src/main/resources/swagger-ui/images/favicon.ico diff --git a/src/main/resources/swagger-ui/images/logo_small.png b/core-rest/src/main/resources/swagger-ui/images/logo_small.png Binary files differindex 5496a65..5496a65 100644 --- a/src/main/resources/swagger-ui/images/logo_small.png +++ b/core-rest/src/main/resources/swagger-ui/images/logo_small.png diff --git a/src/main/resources/swagger-ui/images/pet_store_api.png b/core-rest/src/main/resources/swagger-ui/images/pet_store_api.png Binary files differindex f9f9cd4..f9f9cd4 100644 --- a/src/main/resources/swagger-ui/images/pet_store_api.png +++ b/core-rest/src/main/resources/swagger-ui/images/pet_store_api.png diff --git a/src/main/resources/swagger-ui/images/throbber.gif b/core-rest/src/main/resources/swagger-ui/images/throbber.gif Binary files differindex 0639388..0639388 100644 --- a/src/main/resources/swagger-ui/images/throbber.gif +++ b/core-rest/src/main/resources/swagger-ui/images/throbber.gif diff --git a/src/main/resources/swagger-ui/images/wordnik_api.png b/core-rest/src/main/resources/swagger-ui/images/wordnik_api.png Binary files differindex dca4f14..dca4f14 100644 --- a/src/main/resources/swagger-ui/images/wordnik_api.png +++ b/core-rest/src/main/resources/swagger-ui/images/wordnik_api.png diff --git a/src/main/resources/swagger-ui/index.html b/core-rest/src/main/resources/swagger-ui/index.html index 9691d7d..9691d7d 100644 --- a/src/main/resources/swagger-ui/index.html +++ b/core-rest/src/main/resources/swagger-ui/index.html diff --git a/src/main/resources/swagger-ui/lib/backbone-min.js b/core-rest/src/main/resources/swagger-ui/lib/backbone-min.js index a3f544b..a3f544b 100644 --- a/src/main/resources/swagger-ui/lib/backbone-min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/backbone-min.js diff --git a/src/main/resources/swagger-ui/lib/handlebars-2.0.0.js b/core-rest/src/main/resources/swagger-ui/lib/handlebars-2.0.0.js index 53cf921..53cf921 100644 --- a/src/main/resources/swagger-ui/lib/handlebars-2.0.0.js +++ b/core-rest/src/main/resources/swagger-ui/lib/handlebars-2.0.0.js diff --git a/src/main/resources/swagger-ui/lib/highlight.7.3.pack.js b/core-rest/src/main/resources/swagger-ui/lib/highlight.7.3.pack.js index 9a95a75..9a95a75 100644 --- a/src/main/resources/swagger-ui/lib/highlight.7.3.pack.js +++ b/core-rest/src/main/resources/swagger-ui/lib/highlight.7.3.pack.js diff --git a/src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js b/core-rest/src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js index 066d72c..066d72c 100644 --- a/src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/jquery-1.8.0.min.js diff --git a/src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js b/core-rest/src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js index bcbf248..bcbf248 100644 --- a/src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/jquery.ba-bbq.min.js diff --git a/src/main/resources/swagger-ui/lib/jquery.slideto.min.js b/core-rest/src/main/resources/swagger-ui/lib/jquery.slideto.min.js index ba32cff..ba32cff 100644 --- a/src/main/resources/swagger-ui/lib/jquery.slideto.min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/jquery.slideto.min.js diff --git a/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js b/core-rest/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js index 2adb0d6..2adb0d6 100644 --- a/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/jquery.wiggle.min.js diff --git a/src/main/resources/swagger-ui/lib/marked.js b/core-rest/src/main/resources/swagger-ui/lib/marked.js index c2a678d..c2a678d 100644 --- a/src/main/resources/swagger-ui/lib/marked.js +++ b/core-rest/src/main/resources/swagger-ui/lib/marked.js diff --git a/src/main/resources/swagger-ui/lib/swagger-oauth.js b/core-rest/src/main/resources/swagger-ui/lib/swagger-oauth.js index 3b69fd3..3b69fd3 100644 --- a/src/main/resources/swagger-ui/lib/swagger-oauth.js +++ b/core-rest/src/main/resources/swagger-ui/lib/swagger-oauth.js diff --git a/src/main/resources/swagger-ui/lib/underscore-min.js b/core-rest/src/main/resources/swagger-ui/lib/underscore-min.js index 11f1d96..11f1d96 100644 --- a/src/main/resources/swagger-ui/lib/underscore-min.js +++ b/core-rest/src/main/resources/swagger-ui/lib/underscore-min.js diff --git a/src/main/resources/swagger-ui/lib/underscore-min.map b/core-rest/src/main/resources/swagger-ui/lib/underscore-min.map index cfb76cc..cfb76cc 100644 --- a/src/main/resources/swagger-ui/lib/underscore-min.map +++ b/core-rest/src/main/resources/swagger-ui/lib/underscore-min.map diff --git a/src/main/resources/swagger-ui/o2c.html b/core-rest/src/main/resources/swagger-ui/o2c.html index 88e8bf1..88e8bf1 100644 --- a/src/main/resources/swagger-ui/o2c.html +++ b/core-rest/src/main/resources/swagger-ui/o2c.html diff --git a/src/main/resources/swagger-ui/swagger-ui.js b/core-rest/src/main/resources/swagger-ui/swagger-ui.js index 1396eb8..1396eb8 100644 --- a/src/main/resources/swagger-ui/swagger-ui.js +++ b/core-rest/src/main/resources/swagger-ui/swagger-ui.js diff --git a/src/main/resources/swagger-ui/swagger-ui.min.js b/core-rest/src/main/resources/swagger-ui/swagger-ui.min.js index 966ce7f..966ce7f 100644 --- a/src/main/resources/swagger-ui/swagger-ui.min.js +++ b/core-rest/src/main/resources/swagger-ui/swagger-ui.min.js diff --git a/src/main/scala/xyz/driver/core/auth.scala b/core-rest/src/main/scala/xyz/driver/core/auth.scala index 896bd89..896bd89 100644 --- a/src/main/scala/xyz/driver/core/auth.scala +++ b/core-rest/src/main/scala/xyz/driver/core/auth.scala diff --git a/src/main/scala/xyz/driver/core/generators.scala b/core-rest/src/main/scala/xyz/driver/core/generators.scala index 0a4a7ab..0a4a7ab 100644 --- a/src/main/scala/xyz/driver/core/generators.scala +++ b/core-rest/src/main/scala/xyz/driver/core/generators.scala diff --git a/src/main/scala/xyz/driver/core/json.scala b/core-rest/src/main/scala/xyz/driver/core/json.scala index 48011e4..23373c6 100644 --- a/src/main/scala/xyz/driver/core/json.scala +++ b/core-rest/src/main/scala/xyz/driver/core/json.scala @@ -199,10 +199,18 @@ object json extends PathMatchers with Unmarshallers { } implicit object phoneNumberFormat extends RootJsonFormat[PhoneNumber] { - private val basicFormat = jsonFormat2(PhoneNumber.apply) - override def write(obj: PhoneNumber): JsValue = basicFormat.write(obj) - override def read(json: JsValue): PhoneNumber = { - PhoneNumber.parse(basicFormat.read(json).toString).getOrElse(deserializationError("Invalid phone number")) + + private val basicFormat = jsonFormat3(PhoneNumber.apply) + + def write(obj: PhoneNumber): JsValue = basicFormat.write(obj) + + def read(json: JsValue): PhoneNumber = { + val maybePhone = json match { + case JsString(number) => PhoneNumber.parse(number) + case obj: JsObject => PhoneNumber.parse(basicFormat.read(obj).toString) + case _ => None + } + maybePhone.getOrElse(deserializationError("Invalid phone number")) } } @@ -393,7 +401,6 @@ object json extends PathMatchers with Unmarshallers { case _: ResourceNotFoundException => "ResourceNotFoundException" case _: ExternalServiceException => "ExternalServiceException" case _: ExternalServiceTimeoutException => "ExternalServiceTimeoutException" - case _: DatabaseException => "DatabaseException" } { case "InvalidInputException" => jsonFormat(InvalidInputException, "message") case "InvalidActionException" => jsonFormat(InvalidActionException, "message") @@ -402,7 +409,6 @@ object json extends PathMatchers with Unmarshallers { case "ExternalServiceException" => jsonFormat(ExternalServiceException, "serviceName", "serviceMessage", "serviceException") case "ExternalServiceTimeoutException" => jsonFormat(ExternalServiceTimeoutException, "message") - case "DatabaseException" => jsonFormat(DatabaseException, "message") } } diff --git a/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala b/core-rest/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala index 87946e4..87946e4 100644 --- a/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala diff --git a/src/main/scala/xyz/driver/core/rest/DriverRoute.scala b/core-rest/src/main/scala/xyz/driver/core/rest/DriverRoute.scala index 911e306..b94f611 100644 --- a/src/main/scala/xyz/driver/core/rest/DriverRoute.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/DriverRoute.scala @@ -88,9 +88,6 @@ trait DriverRoute { case e: ExternalServiceTimeoutException => log.error("Service timeout error", e) StatusCodes.GatewayTimeout - case e: DatabaseException => - log.error("Database error", e) - StatusCodes.InternalServerError } { (ctx: RequestContext) => diff --git a/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala b/core-rest/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala index e31635b..e31635b 100644 --- a/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala diff --git a/src/main/scala/xyz/driver/core/rest/PatchDirectives.scala b/core-rest/src/main/scala/xyz/driver/core/rest/PatchDirectives.scala index f33bf9d..f33bf9d 100644 --- a/src/main/scala/xyz/driver/core/rest/PatchDirectives.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/PatchDirectives.scala diff --git a/src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala b/core-rest/src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala index 2854257..2854257 100644 --- a/src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/PooledHttpClient.scala diff --git a/src/main/scala/xyz/driver/core/rest/ProxyRoute.scala b/core-rest/src/main/scala/xyz/driver/core/rest/ProxyRoute.scala index c0e9f99..c0e9f99 100644 --- a/src/main/scala/xyz/driver/core/rest/ProxyRoute.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/ProxyRoute.scala diff --git a/src/main/scala/xyz/driver/core/rest/RestService.scala b/core-rest/src/main/scala/xyz/driver/core/rest/RestService.scala index 91b3550..09d98b8 100644 --- a/src/main/scala/xyz/driver/core/rest/RestService.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/RestService.scala @@ -6,8 +6,6 @@ import akka.stream.Materializer import scala.concurrent.{ExecutionContext, Future} import scalaz.{ListT, OptionT} -import scalaz.syntax.equal._ -import scalaz.Scalaz.stringInstance trait RestService extends Service { @@ -76,10 +74,10 @@ trait RestService extends Service { response: HttpResponse): Future[ListResponse[T]] = { import DefaultJsonProtocol._ val resourceCount = response.headers - .find(_.name() === ContextHeaders.ResourceCount) + .find(_.name() equalsIgnoreCase ContextHeaders.ResourceCount) .map(_.value().toInt) .getOrElse(0) - val meta = ListResponse.Meta(resourceCount, pagination.getOrElse(Pagination(resourceCount, 1))) + val meta = ListResponse.Meta(resourceCount, pagination.getOrElse(Pagination(resourceCount max 1, 1))) Unmarshal(response.entity).to[List[T]].map(ListResponse(_, meta)) } diff --git a/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala b/core-rest/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala index 646fae8..646fae8 100644 --- a/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala diff --git a/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala b/core-rest/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala index 964a5a2..964a5a2 100644 --- a/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala diff --git a/src/main/scala/xyz/driver/core/rest/Swagger.scala b/core-rest/src/main/scala/xyz/driver/core/rest/Swagger.scala index 5ceac54..5ceac54 100644 --- a/src/main/scala/xyz/driver/core/rest/Swagger.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/Swagger.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala index 5007774..5007774 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AlwaysAllowAuthorization.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala index e1a94e1..e1a94e1 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/Authorization.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/Authorization.scala index 1a5e9be..1a5e9be 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/Authorization.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/Authorization.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala index efe28c9..efe28c9 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthorizationResult.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala index 66de4ef..66de4ef 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala diff --git a/src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala index 131e7fc..131e7fc 100644 --- a/src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/ChainedAuthorization.scala diff --git a/src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala b/core-rest/src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala index ff3424d..ff3424d 100644 --- a/src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/directives/AuthDirectives.scala diff --git a/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala b/core-rest/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala index 5a6bbfd..5a6bbfd 100644 --- a/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/directives/CorsDirectives.scala diff --git a/src/main/scala/xyz/driver/core/rest/directives/Directives.scala b/core-rest/src/main/scala/xyz/driver/core/rest/directives/Directives.scala index 0cd4ef1..0cd4ef1 100644 --- a/src/main/scala/xyz/driver/core/rest/directives/Directives.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/directives/Directives.scala diff --git a/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala b/core-rest/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala index 8ba184f..0d60893 100644 --- a/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala @@ -10,6 +10,7 @@ import akka.http.scaladsl.server.PathMatcher.{Matched, Unmatched} import akka.http.scaladsl.server.{PathMatcher, PathMatcher1, PathMatchers => AkkaPathMatchers} import eu.timepit.refined.collection.NonEmpty import eu.timepit.refined.refineV +import xyz.driver.core.domain.PhoneNumber import xyz.driver.core.time.Time import scala.util.control.NonFatal @@ -76,4 +77,15 @@ trait PathMatchers { Some(Revision[T](string)) } + def PhoneInPath: PathMatcher1[PhoneNumber] = new PathMatcher1[PhoneNumber] { + def apply(path: Path) = path match { + case Path.Segment(segment, tail) => + PhoneNumber + .parse(segment) + .map(parsed => Matched(tail, Tuple1(parsed))) + .getOrElse(Unmatched) + case _ => Unmatched + } + } + } diff --git a/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala b/core-rest/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala index 93a9a52..93a9a52 100644 --- a/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala diff --git a/core-rest/src/main/scala/xyz/driver/core/rest/domain.scala b/core-rest/src/main/scala/xyz/driver/core/rest/domain.scala new file mode 100644 index 0000000..7926991 --- /dev/null +++ b/core-rest/src/main/scala/xyz/driver/core/rest/domain.scala @@ -0,0 +1,57 @@ +package xyz.driver.core.rest + +import akka.http.scaladsl.model.{HttpRequest, HttpResponse, ResponseEntity} +import akka.http.scaladsl.unmarshalling.Unmarshal +import akka.stream.Materializer + +import scala.concurrent.Future + +trait Service + +object Service + +trait HttpClient { + def makeRequest(request: HttpRequest): Future[HttpResponse] +} + +trait ServiceTransport { + + def sendRequestGetResponse(context: ServiceRequestContext)(requestStub: HttpRequest): Future[HttpResponse] + + def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest)( + implicit mat: Materializer): Future[Unmarshal[ResponseEntity]] +} + +sealed trait SortingOrder +object SortingOrder { + case object Asc extends SortingOrder + case object Desc extends SortingOrder +} + +final case class SortingField(name: String, sortingOrder: SortingOrder) +final case class Sorting(sortingFields: Seq[SortingField]) + +final case class Pagination(pageSize: Int, pageNumber: Int) { + require(pageSize > 0, "Page size must be greater than zero") + require(pageNumber > 0, "Page number must be greater than zero") + + def offset: Int = pageSize * (pageNumber - 1) +} + +final case class ListResponse[+T](items: Seq[T], meta: ListResponse.Meta) + +object ListResponse { + + def apply[T](items: Seq[T], size: Int, pagination: Option[Pagination]): ListResponse[T] = + ListResponse( + items = items, + meta = ListResponse.Meta(size, pagination.fold(1)(_.pageNumber), pagination.fold(size)(_.pageSize))) + + final case class Meta(itemsCount: Int, pageNumber: Int, pageSize: Int) + + object Meta { + def apply(itemsCount: Int, pagination: Pagination): Meta = + Meta(itemsCount, pagination.pageNumber, pagination.pageSize) + } + +} diff --git a/src/main/scala/xyz/driver/core/rest/errors/serviceException.scala b/core-rest/src/main/scala/xyz/driver/core/rest/errors/serviceException.scala index f2962c9..b43a1d3 100644 --- a/src/main/scala/xyz/driver/core/rest/errors/serviceException.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/errors/serviceException.scala @@ -22,6 +22,3 @@ final case class ExternalServiceException( final case class ExternalServiceTimeoutException(serviceName: String) extends ServiceException(s"$serviceName took too long to respond") - -final case class DatabaseException(override val message: String = "Database access error") - extends ServiceException(message) diff --git a/src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala b/core-rest/src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala index 866476d..866476d 100644 --- a/src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/headers/Traceparent.scala diff --git a/src/main/scala/xyz/driver/core/rest/package.scala b/core-rest/src/main/scala/xyz/driver/core/rest/package.scala index 3be8f02..4620585 100644 --- a/src/main/scala/xyz/driver/core/rest/package.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/package.scala @@ -1,4 +1,4 @@ -package xyz.driver.core.rest +package xyz.driver.core import java.net.InetAddress @@ -7,71 +7,29 @@ import akka.http.scaladsl.model._ import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ -import akka.http.scaladsl.unmarshalling.Unmarshal -import akka.stream.Materializer import akka.stream.scaladsl.Flow import akka.util.ByteString import scalaz.Scalaz.{intInstance, stringInstance} import scalaz.syntax.equal._ import scalaz.{Functor, OptionT} import xyz.driver.core.rest.auth.AuthProvider +import xyz.driver.core.rest.errors.ExternalServiceException import xyz.driver.core.rest.headers.Traceparent import xyz.driver.tracing.TracingDirectives -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} import scala.util.Try -trait Service +package object rest { -object Service - -trait HttpClient { - def makeRequest(request: HttpRequest): Future[HttpResponse] -} - -trait ServiceTransport { - - def sendRequestGetResponse(context: ServiceRequestContext)(requestStub: HttpRequest): Future[HttpResponse] - - def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest)( - implicit mat: Materializer): Future[Unmarshal[ResponseEntity]] -} - -sealed trait SortingOrder -object SortingOrder { - case object Asc extends SortingOrder - case object Desc extends SortingOrder -} - -final case class SortingField(name: String, sortingOrder: SortingOrder) -final case class Sorting(sortingFields: Seq[SortingField]) - -final case class Pagination(pageSize: Int, pageNumber: Int) { - require(pageSize > 0, "Page size must be greater than zero") - require(pageNumber > 0, "Page number must be greater than zero") - - def offset: Int = pageSize * (pageNumber - 1) -} - -final case class ListResponse[+T](items: Seq[T], meta: ListResponse.Meta) - -object ListResponse { - - def apply[T](items: Seq[T], size: Int, pagination: Option[Pagination]): ListResponse[T] = - ListResponse( - items = items, - meta = ListResponse.Meta(size, pagination.fold(1)(_.pageNumber), pagination.fold(size)(_.pageSize))) - - final case class Meta(itemsCount: Int, pageNumber: Int, pageSize: Int) - - object Meta { - def apply(itemsCount: Int, pagination: Pagination): Meta = - Meta(itemsCount, pagination.pageNumber, pagination.pageSize) + implicit class FutureExtensions[T](future: Future[T]) { + def passThroughExternalServiceException(implicit executionContext: ExecutionContext): Future[T] = + future.transform(identity, { + case ExternalServiceException(_, _, Some(e)) => e + case t: Throwable => t + }) } -} - -object `package` { implicit class OptionTRestAdditions[T](optionT: OptionT[Future, T]) { def responseOrNotFound(successCode: StatusCodes.Success = StatusCodes.OK)( implicit F: Functor[Future], diff --git a/src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala b/core-rest/src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala index 55f1a2e..55f1a2e 100644 --- a/src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/serviceDiscovery.scala diff --git a/src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala b/core-rest/src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala index d2e4bc3..d2e4bc3 100644 --- a/src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala +++ b/core-rest/src/main/scala/xyz/driver/core/rest/serviceRequestContext.scala diff --git a/src/main/scala/xyz/driver/core/swagger.scala b/core-rest/src/main/scala/xyz/driver/core/swagger.scala index 0c1e15d..0c1e15d 100644 --- a/src/main/scala/xyz/driver/core/swagger.scala +++ b/core-rest/src/main/scala/xyz/driver/core/swagger.scala diff --git a/src/test/scala/xyz/driver/core/AuthTest.scala b/core-rest/src/test/scala/xyz/driver/core/AuthTest.scala index 2e772fb..8c32aa7 100644 --- a/src/test/scala/xyz/driver/core/AuthTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/AuthTest.scala @@ -9,11 +9,12 @@ import akka.http.scaladsl.model.headers.{ import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ import akka.http.scaladsl.testkit.ScalatestRouteTest +import com.typesafe.scalalogging.Logger import org.scalatest.{FlatSpec, Matchers} +import org.slf4j.helpers.NOPLogger import pdi.jwt.{Jwt, JwtAlgorithm} import xyz.driver.core.auth._ import xyz.driver.core.domain.Email -import xyz.driver.core.logging._ import xyz.driver.core.rest._ import xyz.driver.core.rest.auth._ import xyz.driver.core.time.Time @@ -53,7 +54,7 @@ class AuthTest extends FlatSpec with Matchers with ScalatestRouteTest { val authorization = new ChainedAuthorization[User](tokenAuthorization, basicAuthorization) - val authStatusService = new AuthProvider[User](authorization, NoLogger) { + val authStatusService = new AuthProvider[User](authorization, Logger(NOPLogger.NOP_LOGGER)) { override def authenticatedUser(implicit ctx: ServiceRequestContext): OptionT[Future, User] = OptionT.optionT[Future] { if (ctx.contextHeaders.keySet.contains(AuthProvider.AuthenticationTokenHeader)) { diff --git a/src/test/scala/xyz/driver/core/GeneratorsTest.scala b/core-rest/src/test/scala/xyz/driver/core/GeneratorsTest.scala index 7e740a4..7e740a4 100644 --- a/src/test/scala/xyz/driver/core/GeneratorsTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/GeneratorsTest.scala diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/core-rest/src/test/scala/xyz/driver/core/JsonTest.scala index 3e68d90..9f54c04 100644 --- a/src/test/scala/xyz/driver/core/JsonTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/JsonTest.scala @@ -274,6 +274,21 @@ class JsonTest extends WordSpec with Matchers with Inspectors { json.phoneNumberFormat.read(phoneJson) }.getMessage shouldBe "Invalid phone number" } + + "parse phone number from string" in { + JsString("+14243039608").convertTo[PhoneNumber] shouldBe PhoneNumber("1", "4243039608") + } + } + + "Path matcher for PhoneNumber" should { + "read valid phone number" in { + val string = "+14243039608x23" + val phone = PhoneNumber("1", "4243039608", Some("23")) + + val matcher = PathMatcher("foo") / PhoneInPath + + matcher(Uri.Path("foo") / string / "bar") shouldBe Matched(Uri.Path./("bar"), Tuple1(phone)) + } } "Json format for ADT mappings" should { diff --git a/src/test/scala/xyz/driver/core/TestTypes.scala b/core-rest/src/test/scala/xyz/driver/core/TestTypes.scala index bb25deb..bb25deb 100644 --- a/src/test/scala/xyz/driver/core/TestTypes.scala +++ b/core-rest/src/test/scala/xyz/driver/core/TestTypes.scala diff --git a/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala b/core-rest/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala index 86cf8b5..d1172fa 100644 --- a/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala @@ -8,9 +8,8 @@ import akka.http.scaladsl.server.{Directives, RejectionHandler, Route} import akka.http.scaladsl.testkit.ScalatestRouteTest import com.typesafe.scalalogging.Logger import org.scalatest.{AsyncFlatSpec, Matchers} -import xyz.driver.core.FutureExtensions +import org.slf4j.helpers.NOPLogger import xyz.driver.core.json.serviceExceptionFormat -import xyz.driver.core.logging.NoLogger import xyz.driver.core.rest.errors._ import scala.concurrent.Future @@ -18,7 +17,7 @@ import scala.concurrent.Future class DriverRouteTest extends AsyncFlatSpec with ScalatestRouteTest with SprayJsonSupport with Matchers with Directives { class TestRoute(override val route: Route) extends DriverRoute { - override def log: Logger = NoLogger + override def log: Logger = Logger(NOPLogger.NOP_LOGGER) } "DriverRoute" should "respond with 200 OK for a basic route" in { @@ -95,16 +94,6 @@ class DriverRouteTest } } - it should "respond with a 500 for DatabaseException" in { - val route = new TestRoute(akkaComplete(Future.failed[String](DatabaseException()))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.InternalServerError - responseAs[ServiceException] shouldBe DatabaseException() - } - } - it should "add a `Connection: close` header to avoid clashing with envoy's timeouts" in { val rejectionHandler = RejectionHandler.newBuilder().handleNotFound(complete(StatusCodes.NotFound)).result() val route = new TestRoute(handleRejections(rejectionHandler)((get & path("foo"))(complete("OK")))) diff --git a/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala b/core-rest/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala index 987717d..987717d 100644 --- a/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala diff --git a/src/test/scala/xyz/driver/core/rest/RestTest.scala b/core-rest/src/test/scala/xyz/driver/core/rest/RestTest.scala index 19e4ed1..19e4ed1 100644 --- a/src/test/scala/xyz/driver/core/rest/RestTest.scala +++ b/core-rest/src/test/scala/xyz/driver/core/rest/RestTest.scala diff --git a/src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala b/core-storage/src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala index b5e8678..388b8f5 100644 --- a/src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala +++ b/core-storage/src/main/scala/xyz/driver/core/storage/AliyunBlobStorage.scala @@ -3,6 +3,7 @@ package xyz.driver.core.storage import java.io.ByteArrayInputStream import java.net.URL import java.nio.file.Path +import java.time.Clock import java.util.Date import akka.Done @@ -11,7 +12,6 @@ import akka.util.ByteString import com.aliyun.oss.OSSClient import com.aliyun.oss.model.ObjectPermission import com.typesafe.config.Config -import xyz.driver.core.time.provider.TimeProvider import scala.collection.JavaConverters._ import scala.concurrent.duration.Duration @@ -20,7 +20,7 @@ import scala.concurrent.{ExecutionContext, Future} class AliyunBlobStorage( client: OSSClient, bucketId: String, - timeProvider: TimeProvider, + clock: Clock, chunkSize: Int = AliyunBlobStorage.DefaultChunkSize)(implicit ec: ExecutionContext) extends SignedBlobStorage { override def uploadContent(name: String, content: Array[Byte]): Future[String] = Future { @@ -81,7 +81,7 @@ class AliyunBlobStorage( override def signedDownloadUrl(name: String, duration: Duration): Future[Option[URL]] = Future { if (client.doesObjectExist(bucketId, name)) { - val expiration = new Date(timeProvider.currentTime().advanceBy(duration).millis) + val expiration = new Date(clock.millis() + duration.toMillis) Some(client.generatePresignedUrl(bucketId, name, expiration)) } else { None @@ -92,17 +92,18 @@ class AliyunBlobStorage( object AliyunBlobStorage { val DefaultChunkSize: Int = 8192 - def apply(config: Config, bucketId: String, timeProvider: TimeProvider)( - implicit ec: ExecutionContext): AliyunBlobStorage = { + def apply(config: Config, bucketId: String, clock: Clock)(implicit ec: ExecutionContext): AliyunBlobStorage = { val clientId = config.getString("storage.aliyun.clientId") val clientSecret = config.getString("storage.aliyun.clientSecret") val endpoint = config.getString("storage.aliyun.endpoint") - this(clientId, clientSecret, endpoint, bucketId, timeProvider) + this(clientId, clientSecret, endpoint, bucketId, clock) } - def apply(clientId: String, clientSecret: String, endpoint: String, bucketId: String, timeProvider: TimeProvider)( + def apply(clientId: String, clientSecret: String, region: String, bucketId: String, clock: Clock)( implicit ec: ExecutionContext): AliyunBlobStorage = { - val client = new OSSClient(endpoint, clientId, clientSecret) - new AliyunBlobStorage(client, bucketId, timeProvider) + // https://www.alibabacloud.com/help/doc-detail/31837.htm + val endpoint = s"https://oss-$region.aliyuncs.com" + val client = new OSSClient(endpoint, clientId, clientSecret) + new AliyunBlobStorage(client, bucketId, clock) } } diff --git a/src/main/scala/xyz/driver/core/storage/BlobStorage.scala b/core-storage/src/main/scala/xyz/driver/core/storage/BlobStorage.scala index 0cde96a..0cde96a 100644 --- a/src/main/scala/xyz/driver/core/storage/BlobStorage.scala +++ b/core-storage/src/main/scala/xyz/driver/core/storage/BlobStorage.scala diff --git a/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala b/core-storage/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala index e12c73d..e12c73d 100644 --- a/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala +++ b/core-storage/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala diff --git a/src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala b/core-storage/src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala index 95164c7..95164c7 100644 --- a/src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala +++ b/core-storage/src/main/scala/xyz/driver/core/storage/GcsBlobStorage.scala diff --git a/src/main/scala/xyz/driver/core/storage/channelStreams.scala b/core-storage/src/main/scala/xyz/driver/core/storage/channelStreams.scala index fc652be..fc652be 100644 --- a/src/main/scala/xyz/driver/core/storage/channelStreams.scala +++ b/core-storage/src/main/scala/xyz/driver/core/storage/channelStreams.scala diff --git a/src/test/scala/xyz/driver/core/BlobStorageTest.scala b/core-storage/src/test/scala/xyz/driver/core/BlobStorageTest.scala index 811cc60..811cc60 100644 --- a/src/test/scala/xyz/driver/core/BlobStorageTest.scala +++ b/core-storage/src/test/scala/xyz/driver/core/BlobStorageTest.scala diff --git a/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala b/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala new file mode 100644 index 0000000..36e3596 --- /dev/null +++ b/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala @@ -0,0 +1,79 @@ +package akka.http.scaladsl.testkit + +import akka.actor.ActorSystem +import akka.http.javadsl.testkit.DefaultHostInfo +import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.headers.{Host, RawHeader} +import akka.http.scaladsl.server._ +import akka.http.scaladsl.settings.RoutingSettings +import akka.http.scaladsl.unmarshalling.Unmarshal +import akka.stream.{ActorMaterializer, Materializer} +import org.slf4j.MDC +import xyz.driver.core.rest.errors.ExternalServiceException +import xyz.driver.core.rest.{ContextHeaders, ServiceRequestContext, ServiceTransport} + +import scala.concurrent.{ExecutionContextExecutor, Future} +import scala.concurrent.duration._ + +class TestRouteServiceTransport(route: Route, routeTimeout: FiniteDuration = 10 seconds)( + implicit executor: ExecutionContextExecutor, + system: ActorSystem) + extends ServiceTransport with RouteTestResultComponent { + + def defaultHost: DefaultHostInfo = DefaultHostInfo(Host("example.com"), securedConnection = false) + def routingLog: RoutingLog = RoutingLog(system.log) + def routingSettings: RoutingSettings = RoutingSettings.apply(system) + implicit val materializer = ActorMaterializer() + + override def sendRequestGetResponse(context: ServiceRequestContext)( + requestStub: HttpRequest): Future[HttpResponse] = { + + val request = requestStub + .withHeaders(requestStub.headers ++ context.contextHeaders.toSeq.map { + case (ContextHeaders.TrackingIdHeader, _) => + RawHeader(ContextHeaders.TrackingIdHeader, context.trackingId) + case (ContextHeaders.StacktraceHeader, _) => + RawHeader( + ContextHeaders.StacktraceHeader, + Option(MDC.get("stack")) + .orElse(context.contextHeaders.get(ContextHeaders.StacktraceHeader)) + .getOrElse("")) + case (header, headerValue) => RawHeader(header, headerValue) + }: _*) + + // Code below is forked from `akka.http.scaladsl.testkit.RouteTest.TildeArrow.injectIntoRoute`, + // because it doesn't allow to call just this code outside of the DSL for testkit tests + val routeTestResult = new RouteTestResult(routeTimeout) + + val effectiveRequest = request + + val log = routingLog.requestLog(effectiveRequest) + val ctx = new RequestContextImpl(effectiveRequest, log, routingSettings)(executor, materializer) + val sealedExceptionHandler = ExceptionHandler.default(routingSettings) + val semiSealedRoute = Directives.handleExceptions(sealedExceptionHandler)(route) + val deferrableRouteResult = semiSealedRoute(ctx) + + deferrableRouteResult.map(r => { routeTestResult.handleResult(r); routeTestResult.response }) + } + + override def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest)( + implicit mat: Materializer): Future[Unmarshal[ResponseEntity]] = { + sendRequestGetResponse(context)(requestStub) flatMap { response => + if (response.status == StatusCodes.NotFound) { + Future.successful(Unmarshal(HttpEntity.Empty: ResponseEntity)) + } else if (response.status.isFailure()) { + val serviceCalled = s"${requestStub.method} ${requestStub.uri}" + Unmarshal(response.entity).to[String] flatMap { errorString => + import spray.json._ + import xyz.driver.core.json._ + val serviceException = scala.util.Try(serviceExceptionFormat.read(errorString.parseJson)).toOption + Future.failed(ExternalServiceException(serviceCalled, errorString, serviceException)) + } + } else { + Future.successful(Unmarshal(response.entity)) + } + } + } + + override def failTest(msg: String): Nothing = throw new Exception(msg) +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala new file mode 100644 index 0000000..8ce33f6 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala @@ -0,0 +1,28 @@ +package xyz.driver.core.testkit + +import java.util.concurrent.Executors + +import akka.actor.ActorSystem +import akka.http.scaladsl.server.directives.FutureDirectives +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import org.scalamock.scalatest.AsyncMockFactory +import org.scalatest.AsyncFlatSpec + +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} + +trait AsyncDatabaseBackedRouteTest + extends AsyncFlatSpec with DriverFunctionalTest with AsyncMockFactory with ScalatestRouteTest { + + def route: FutureDirectives + + val defaultTimeOut: FiniteDuration = 5.seconds + implicit def default(implicit system: ActorSystem): RouteTestTimeout = RouteTestTimeout(defaultTimeOut) + + override implicit val executor: ExecutionContextExecutor = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) + override implicit def executionContext: ExecutionContext = executor + + override def beforeAll: Unit = super.beforeAll() + override def afterAll: Unit = super.afterAll() +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala new file mode 100644 index 0000000..f4c793b --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala @@ -0,0 +1,18 @@ +package xyz.driver.core.testkit + +import com.typesafe.scalalogging.Logger +import org.scalatest.{Matchers, Suite} +import org.slf4j.helpers.NOPLogger +import xyz.driver.core.time.Time +import xyz.driver.core.time.provider.{SpecificTimeProvider, TimeProvider} + +trait DriverFunctionalTest extends Matchers with postgres.DockerPostgresFixtureDatabase { + self: Suite => + + // Needed to reset some external libraries timezones (e.g., HSQLDB) + // for local development matching CI and deployed environment + java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("Etc/UTC")) + + val log: Logger = Logger(NOPLogger.NOP_LOGGER) + val timeProvider: TimeProvider = new SpecificTimeProvider(Time(100000)) +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala new file mode 100644 index 0000000..aa07668 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala @@ -0,0 +1,60 @@ +package xyz.driver.core.testkit + +import java.nio.file.{Files, Path} + +import scala.concurrent.Await +import scala.concurrent.duration._ + +import org.scalatest.{BeforeAndAfterEach, Suite} +import slick.basic.BasicProfile +import slick.jdbc.JdbcProfile +import slick.relational.RelationalProfile + +trait FixtureDatabase[P <: BasicProfile] { self: Suite => + val profile: P + val database: P#Backend#DatabaseDef +} + +trait CreateAndDropSchemaForEach[P <: RelationalProfile] extends BeforeAndAfterEach { + self: Suite with FixtureDatabase[P] => + import profile.api._ + def schema: profile.SchemaDescription + + override protected def beforeEach() = { + Await.result(database.run(schema.create), 5.seconds) + super.beforeEach() + } + + override protected def afterEach() = { + try super.afterEach() + finally Await.result(database.run(schema.drop), 5.seconds) + } +} + +trait TruncateSchemaAfterEach[P <: RelationalProfile] extends BeforeAndAfterEach { + self: Suite with FixtureDatabase[P] => + import profile.api._ + + def schema: profile.SchemaDescription + + override protected def afterEach() = { + try super.afterEach() + finally Await.result(database.run(schema.truncate), 5.seconds) + } +} + +trait InsertBeforeEach[P <: JdbcProfile] extends BeforeAndAfterEach { self: Suite with FixtureDatabase[P] => + import profile.api._ + + val insertsFiles: Set[Path] + + def insertTestData(insertsFile: Path): DBIO[Int] = { + val rawInserts = new String(Files.readAllBytes(insertsFile), "UTF-8") + sql"#$rawInserts".asUpdate + } + + override protected def beforeEach(): Unit = { + concurrent.Await.result(database.run(DBIO.sequence(insertsFiles.toList.map(insertTestData))), 30.seconds) + super.beforeEach() + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala new file mode 100644 index 0000000..db02bbb --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala @@ -0,0 +1,70 @@ +package xyz.driver.core.testkit + +import akka.actor.ActorSystem +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import spray.json._ +import xyz.driver.core.Name +import xyz.driver.core.rest.{RestService, ServiceRequestContext, ServiceTransport} + +import scala.concurrent.{ExecutionContext, Future} +import scalaz.{ListT, OptionT} + +object RestDatabaseResetService { + import DefaultJsonProtocol._ + import xyz.driver.core.json._ + + final case class Application(name: Name[Application], tag: String) + + final case class Environment(name: Name[Environment], applications: Seq[Application], status: String) + + implicit val applicationJsonFormat = jsonFormat2(Application) + + implicit val environmentJsonFormat = jsonFormat3(Environment) +} + +class RestDatabaseResetService( + transport: ServiceTransport, + baseUri: Uri, + actorSystem: ActorSystem, + executionContext: ExecutionContext) + extends RestService { + + import DefaultJsonProtocol._ + import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ + import RestDatabaseResetService._ + + protected implicit val exec = executionContext + protected implicit val materializer = ActorMaterializer()(actorSystem) + + def getAvailableEnvironments(): ListT[Future, String] = { + val request = get(baseUri, s"/v1/environment") + listResponse[String](transport.sendRequest(new ServiceRequestContext())(request)) + } + + def getEnvironmentDetails(envName: Name[Environment]): ListT[Future, Environment] = { + val requestStub = get(baseUri, s"/v1/environment/$envName") + listResponse[Environment](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def getEnvironmentStatus(envName: Name[Environment]): OptionT[Future, String] = { + val requestStub = get(baseUri, s"/v1/environment/$envName/status") + optionalResponse[String](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def resetEnvironmentStatus(envName: Name[Environment], newStatus: String): OptionT[Future, String] = { + val requestStub = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/environment/$envName/status")) + optionalResponse[String](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def resetEnvironmentApplicationsData(envName: Name[Environment]): OptionT[Future, Unit] = { + val requestStub = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/environment/$envName/reset")) + unitResponse(transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def updateEnvironmentState(updatedEnvironment: Environment)( + implicit ctx: ServiceRequestContext): OptionT[Future, Environment] = { + val requestStub = postJson(baseUri, s"/v1/environment/${updatedEnvironment.name}", updatedEnvironment.toJson) + optionalResponse[Environment](transport.sendRequest(ctx)(requestStub)) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala new file mode 100644 index 0000000..8a4c3e8 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala @@ -0,0 +1,26 @@ +package xyz.driver.test.hsql + +import java.nio.file.{Files, Paths} + +import slick.dbio.DBIO + +trait HsqlTestDatabase { + def insertTestData(database: xyz.driver.core.database.Database, filePath: String): DBIO[Int] = { + import database.profile.api._ + + val file = Paths.get(filePath) + val sqlLine = new String(Files.readAllBytes(file), "UTF-8") + + val createProcedure = + sqlu"""CREATE PROCEDURE INSERT_TEST_DATA() + MODIFIES SQL DATA + BEGIN ATOMIC + #$sqlLine + END; + """ + val callProcedure = sqlu"""{call INSERT_TEST_DATA()}""" + val dropProcedure = sqlu"""drop PROCEDURE INSERT_TEST_DATA;""" + + createProcedure >> callProcedure >> dropProcedure + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala new file mode 100644 index 0000000..41e6e0d --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala @@ -0,0 +1,46 @@ +package xyz.driver.core.testkit +package postgres + +import xyz.driver.core.make + +trait DockerPostgresDatabase { + import com.spotify.docker.client._ + import com.spotify.docker.client.messages._ + + lazy val dockerClient: DockerClient = DefaultDockerClient.fromEnv().build() + + val postgresVersion: String = "9.6" + + def setupDockerDatabase( + username: String = "postgres", + password: String = "postgres", + database: String = "postgres", + hostPort: Int = 15432): String = { + import collection.JavaConverters._ + + dockerClient.pull(s"postgres:$postgresVersion") + + val portBindings: Map[String, List[PortBinding]] = Map("5432" -> List(PortBinding.of("0.0.0.0", hostPort))) + val portBindingsJava = portBindings.mapValues(_.asJava).asJava + val hostConfig = HostConfig.builder().portBindings(portBindingsJava).build() + val containerConfig = + ContainerConfig + .builder() + .hostConfig(hostConfig) + .image(s"postgres:$postgresVersion") + .exposedPorts("5432") + .env( + s"POSTGRES_USER=$username", + s"POSTGRES_DB=$database", + s"POSTGRES_PASSWORD=$password" + ) + .build() + + make(dockerClient.createContainer(containerConfig).id())(dockerClient.startContainer) + } + + def killDockerDatabase(containerId: String): Unit = { + dockerClient.killContainer(containerId) + dockerClient.removeContainer(containerId) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala new file mode 100644 index 0000000..3e00bf5 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala @@ -0,0 +1,74 @@ +package xyz.driver.core.testkit +package postgres + +import java.net.ServerSocket + +import org.scalatest.{BeforeAndAfterAll, Suite} +import slick.jdbc.PostgresProfile +import xyz.driver.core.using + +import scala.concurrent.Await +import scala.concurrent.duration._ + +trait DockerPostgresFixtureDatabase + extends FixtureDatabase[PostgresProfile] with DockerPostgresDatabase with BeforeAndAfterAll { + self: Suite => + private val dbName: String = sys.env.getOrElse("TEST_DB_NAME", "postgres") + private val username: String = sys.env.getOrElse("TEST_DB_USERNAME", "postgres") + private val password: String = sys.env.getOrElse("TEST_DB_PASSWORD", "postgres") + private val port: Int = sys.env.get("TEST_DB_PORT").fold(getRandomPort())(_.toInt) + + private val enabled: Boolean = !sys.env.get("DISABLE_DOCKER_TEST_DB").contains("true") + + protected val connectionTimeout: Duration = 16.seconds + + final val profile = PostgresProfile + final override val database = profile.backend.Database.forURL( + driver = "org.postgresql.Driver", + url = s"jdbc:postgresql://localhost:$port/$dbName", + user = username, + password = password) + + object driverDatabase extends xyz.driver.core.database.Database { + override val profile = self.profile + override val database = self.database + } + + private def getRandomPort(): Int = using(new ServerSocket(0))(_.getLocalPort()) + + @SuppressWarnings(Array("org.wartremover.warts.Var")) + private var dockerContainerId: Option[String] = None + + private def waitForContainer(): Unit = { + import concurrent.ExecutionContext.Implicits.global + import profile.api._ + + val expiration = System.currentTimeMillis() + connectionTimeout.toMillis + + val query = sql"SELECT 1;".as[Int] + def dbReady = Await.result( + database + .run(query) + .map(_ == Vector(1)) + .recover({ case _: org.postgresql.util.PSQLException => false }), + connectionTimeout + ) + + while (!dbReady && System.currentTimeMillis() < expiration) { + Thread.sleep(100) + } + } + + override protected def beforeAll(): Unit = { + if (enabled) { + dockerContainerId = Some(super.setupDockerDatabase(username, password, dbName, port)) + waitForContainer() + } + super.beforeAll() + } + + override protected def afterAll(): Unit = { + try super.afterAll() + finally dockerContainerId.foreach(killDockerDatabase) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/test/renames.scala b/core-testkit/src/main/scala/xyz/driver/test/renames.scala new file mode 100644 index 0000000..a9dfe04 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/test/renames.scala @@ -0,0 +1,46 @@ +package xyz.driver + +import xyz.driver.core.testkit +import slick.basic.BasicProfile +import slick.jdbc.JdbcProfile +import slick.relational.RelationalProfile + +package object test { + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type AsyncDatabaseBackedRouteTest = testkit.AsyncDatabaseBackedRouteTest + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type DriverFunctionalTest = testkit.DriverFunctionalTest + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type FixtureDatabase[P <: BasicProfile] = testkit.FixtureDatabase[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type CreateAndDropSchemaForEach[P <: RelationalProfile] = testkit.CreateAndDropSchemaForEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type TruncateSchemaAfterEach[P <: RelationalProfile] = testkit.TruncateSchemaAfterEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type InsertBeforeEach[P <: JdbcProfile] = testkit.InsertBeforeEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type RestDatabaseResetService = testkit.RestDatabaseResetService + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + val RestDatabaseResetService: testkit.RestDatabaseResetService.type = testkit.RestDatabaseResetService + +} + +package test { + package object postgres { + + @deprecated("moved to package `xyz.driver.core.testkit.postgres`", "2.0") + type DockerPostgresDatabase = xyz.driver.core.testkit.postgres.DockerPostgresDatabase + + @deprecated("moved to package `xyz.driver.core.testkit.postgres`", "2.0") + type DockerPostgresFixtureDatabase = xyz.driver.core.testkit.postgres.DockerPostgresFixtureDatabase + + } +} diff --git a/core-types/src/main/scala/xyz/driver/core/DatabaseException.scala b/core-types/src/main/scala/xyz/driver/core/DatabaseException.scala new file mode 100644 index 0000000..3babf5c --- /dev/null +++ b/core-types/src/main/scala/xyz/driver/core/DatabaseException.scala @@ -0,0 +1,3 @@ +package xyz.driver.core + +final case class DatabaseException(message: String = "Database access error") extends RuntimeException(message) diff --git a/src/main/scala/xyz/driver/core/core.scala b/core-types/src/main/scala/xyz/driver/core/core.scala index 11c1ffe..d88bf09 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/core-types/src/main/scala/xyz/driver/core/core.scala @@ -3,11 +3,8 @@ package xyz.driver import eu.timepit.refined.api.{Refined, Validate} import eu.timepit.refined.collection.NonEmpty import scalaz.{Equal, Monad, OptionT} -import xyz.driver.core.rest.errors.ExternalServiceException import xyz.driver.core.tagging.Tagged -import scala.concurrent.{ExecutionContext, Future} - // TODO: this package seems too complex, look at all the features we need! import scala.language.{higherKinds, implicitConversions, reflectiveCalls} @@ -53,13 +50,6 @@ package object core { OptionT.optionT[H](monadT(monadicValue)(_ => Option(()))) } - implicit class FutureExtensions[T](future: Future[T]) { - def passThroughExternalServiceException(implicit executionContext: ExecutionContext): Future[T] = - future.transform(identity, { - case ExternalServiceException(_, _, Some(e)) => e - case t: Throwable => t - }) - } } package core { diff --git a/src/main/scala/xyz/driver/core/date.scala b/core-types/src/main/scala/xyz/driver/core/date.scala index 5454093..5454093 100644 --- a/src/main/scala/xyz/driver/core/date.scala +++ b/core-types/src/main/scala/xyz/driver/core/date.scala diff --git a/core-types/src/main/scala/xyz/driver/core/deprecations.scala b/core-types/src/main/scala/xyz/driver/core/deprecations.scala new file mode 100644 index 0000000..401e6c6 --- /dev/null +++ b/core-types/src/main/scala/xyz/driver/core/deprecations.scala @@ -0,0 +1,5 @@ +package xyz.driver.core.rest + +package object errors { + //type DatabaseException = xyz.driver.core.errors.DatabaseException +} diff --git a/core-types/src/main/scala/xyz/driver/core/domain.scala b/core-types/src/main/scala/xyz/driver/core/domain.scala new file mode 100644 index 0000000..f3b8337 --- /dev/null +++ b/core-types/src/main/scala/xyz/driver/core/domain.scala @@ -0,0 +1,73 @@ +package xyz.driver.core + +import com.google.i18n.phonenumbers.PhoneNumberUtil +import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat +import scalaz.Equal +import scalaz.std.string._ +import scalaz.syntax.equal._ + +import scala.util.Try +import scala.util.control.NonFatal + +object domain { + + final case class Email(username: String, domain: String) { + + def value: String = toString + + override def toString: String = username + "@" + domain + } + + object Email { + implicit val emailEqual: Equal[Email] = Equal.equal { + case (left, right) => left.toString.toLowerCase === right.toString.toLowerCase + } + + def parse(emailString: String): Option[Email] = { + Some(emailString.split("@")) collect { + case Array(username, domain) => Email(username, domain) + } + } + } + + final case class PhoneNumber(countryCode: String, number: String, extension: Option[String] = None) { + + def hasExtension: Boolean = extension.isDefined + + /** This is a more human-friendly alias for #toE164String() */ + def toCompactString: String = s"+$countryCode$number${extension.fold("")(";ext=" + _)}" + + /** Outputs the phone number in a E.164-compliant way, e.g. +14151234567 */ + def toE164String: String = toCompactString + + /** + * Outputs the phone number in a "readable" way, e.g. "+1 415-123-45-67 ext. 1234" + * @throws IllegalStateException if the contents of this object is not a valid phone number + */ + @throws[IllegalStateException] + def toHumanReadableString: String = + try { + val phoneNumber = PhoneNumber.phoneUtil.parse(toE164String, "US") + PhoneNumber.phoneUtil.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL) + } catch { + case NonFatal(e) => throw new IllegalStateException(s"$toString is not a valid number", e) + } + + override def toString: String = s"+$countryCode $number${extension.fold("")(" ext. " + _)}" + } + + object PhoneNumber { + + private[PhoneNumber] val phoneUtil = PhoneNumberUtil.getInstance() + + def parse(phoneNumber: String): Option[PhoneNumber] = { + val validated = Try(phoneUtil.parseAndKeepRawInput(phoneNumber, "US")).toOption.filter(phoneUtil.isValidNumber) + validated.map { pn => + PhoneNumber( + pn.getCountryCode.toString, + pn.getNationalNumber.toString, + Option(pn.getExtension).filter(_.nonEmpty)) + } + } + } +} diff --git a/core-types/src/main/scala/xyz/driver/core/generators.scala b/core-types/src/main/scala/xyz/driver/core/generators.scala new file mode 100644 index 0000000..d00b6dd --- /dev/null +++ b/core-types/src/main/scala/xyz/driver/core/generators.scala @@ -0,0 +1,143 @@ +package xyz.driver.core + +import enumeratum._ +import java.math.MathContext +import java.time.{Instant, LocalDate, ZoneOffset} +import java.util.UUID + +import xyz.driver.core.time.{Time, TimeOfDay, TimeRange} +import xyz.driver.core.date.{Date, DayOfWeek} + +import scala.reflect.ClassTag +import scala.util.Random +import eu.timepit.refined.refineV +import eu.timepit.refined.api.Refined +import eu.timepit.refined.collection._ + +object generators { + + private val random = new Random + import random._ + private val secureRandom = new java.security.SecureRandom() + + private val DefaultMaxLength = 10 + private val StringLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ".toSet + private val NonAmbigiousCharacters = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" + private val Numbers = "0123456789" + + private def nextTokenString(length: Int, chars: IndexedSeq[Char]): String = { + val builder = new StringBuilder + for (_ <- 0 until length) { + builder += chars(secureRandom.nextInt(chars.length)) + } + builder.result() + } + + /** Creates a random invitation token. + * + * This token is meant fo human input and avoids using ambiguous characters such as 'O' and '0'. It + * therefore contains less entropy and is not meant to be used as a cryptographic secret. */ + @deprecated( + "The term 'token' is too generic and security and readability conventions are not well defined. " + + "Services should implement their own version that suits their security requirements.", + "1.11.0" + ) + def nextToken(length: Int): String = nextTokenString(length, NonAmbigiousCharacters) + + @deprecated( + "The term 'token' is too generic and security and readability conventions are not well defined. " + + "Services should implement their own version that suits their security requirements.", + "1.11.0" + ) + def nextNumericToken(length: Int): String = nextTokenString(length, Numbers) + + def nextInt(maxValue: Int, minValue: Int = 0): Int = random.nextInt(maxValue - minValue) + minValue + + def nextBoolean(): Boolean = random.nextBoolean() + + def nextDouble(): Double = random.nextDouble() + + def nextId[T](): Id[T] = Id[T](nextUuid().toString) + + def nextId[T](maxLength: Int): Id[T] = Id[T](nextString(maxLength)) + + def nextNumericId[T](): Id[T] = Id[T](nextLong.abs.toString) + + def nextNumericId[T](maxValue: Int): Id[T] = Id[T](nextInt(maxValue).toString) + + def nextName[T](maxLength: Int = DefaultMaxLength): Name[T] = Name[T](nextString(maxLength)) + + def nextNonEmptyName[T](maxLength: Int = DefaultMaxLength): NonEmptyName[T] = + NonEmptyName[T](nextNonEmptyString(maxLength)) + + def nextUuid(): UUID = java.util.UUID.randomUUID + + def nextRevision[T](): Revision[T] = Revision[T](nextUuid().toString) + + def nextString(maxLength: Int = DefaultMaxLength): String = + (oneOf[Char](StringLetters) +: arrayOf(oneOf[Char](StringLetters), maxLength - 1)).mkString + + def nextNonEmptyString(maxLength: Int = DefaultMaxLength): String Refined NonEmpty = { + refineV[NonEmpty]( + (oneOf[Char](StringLetters) +: arrayOf(oneOf[Char](StringLetters), maxLength - 1)).mkString + ).right.get + } + + def nextOption[T](value: => T): Option[T] = if (nextBoolean()) Option(value) else None + + def nextPair[L, R](left: => L, right: => R): (L, R) = (left, right) + + def nextTriad[F, S, T](first: => F, second: => S, third: => T): (F, S, T) = (first, second, third) + + def nextInstant(): Instant = Instant.ofEpochMilli(math.abs(nextLong() % System.currentTimeMillis)) + + def nextTime(): Time = nextInstant() + + def nextTimeOfDay: TimeOfDay = TimeOfDay(java.time.LocalTime.MIN.plusSeconds(nextLong), java.util.TimeZone.getDefault) + + def nextTimeRange(): TimeRange = { + val oneTime = nextTime() + val anotherTime = nextTime() + + TimeRange( + Time(scala.math.min(oneTime.millis, anotherTime.millis)), + Time(scala.math.max(oneTime.millis, anotherTime.millis))) + } + + def nextDate(): Date = nextTime().toDate(java.util.TimeZone.getTimeZone("UTC")) + + def nextLocalDate(): LocalDate = nextInstant().atZone(ZoneOffset.UTC).toLocalDate + + def nextDayOfWeek(): DayOfWeek = oneOf(DayOfWeek.All) + + def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal = + BigDecimal(multiplier * nextDouble, new MathContext(precision)) + + def oneOf[T](items: T*): T = oneOf(items.toSet) + + def oneOf[T](items: Set[T]): T = items.toSeq(nextInt(items.size)) + + def oneOf[T <: EnumEntry](enum: Enum[T]): T = oneOf(enum.values: _*) + + def arrayOf[T: ClassTag](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Array[T] = + Array.fill(nextInt(maxLength, minLength))(generator) + + def seqOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Seq[T] = + Seq.fill(nextInt(maxLength, minLength))(generator) + + def vectorOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Vector[T] = + Vector.fill(nextInt(maxLength, minLength))(generator) + + def listOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): List[T] = + List.fill(nextInt(maxLength, minLength))(generator) + + def setOf[T](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Set[T] = + seqOf(generator, maxLength, minLength).toSet + + def mapOf[K, V]( + keyGenerator: => K, + valueGenerator: => V, + maxLength: Int = DefaultMaxLength, + minLength: Int = 0): Map[K, V] = + seqOf(nextPair(keyGenerator, valueGenerator), maxLength, minLength).toMap +} diff --git a/src/main/scala/xyz/driver/core/tagging/tagging.scala b/core-types/src/main/scala/xyz/driver/core/tagging/tagging.scala index 5b6599e..5b6599e 100644 --- a/src/main/scala/xyz/driver/core/tagging/tagging.scala +++ b/core-types/src/main/scala/xyz/driver/core/tagging/tagging.scala diff --git a/src/main/scala/xyz/driver/core/time.scala b/core-types/src/main/scala/xyz/driver/core/time.scala index 1622068..1622068 100644 --- a/src/main/scala/xyz/driver/core/time.scala +++ b/core-types/src/main/scala/xyz/driver/core/time.scala diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/core-types/src/test/scala/xyz/driver/core/CoreTest.scala index 1cd7a7c..1cd7a7c 100644 --- a/src/test/scala/xyz/driver/core/CoreTest.scala +++ b/core-types/src/test/scala/xyz/driver/core/CoreTest.scala diff --git a/src/test/scala/xyz/driver/core/DateTest.scala b/core-types/src/test/scala/xyz/driver/core/DateTest.scala index 0432040..0432040 100644 --- a/src/test/scala/xyz/driver/core/DateTest.scala +++ b/core-types/src/test/scala/xyz/driver/core/DateTest.scala diff --git a/src/test/scala/xyz/driver/core/PhoneNumberTest.scala b/core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala index 384c7be..729302b 100644 --- a/src/test/scala/xyz/driver/core/PhoneNumberTest.scala +++ b/core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala @@ -44,6 +44,21 @@ class PhoneNumberTest extends FlatSpec with Matchers { PhoneNumber.parse("+86 134 52 52 2256") shouldBe Some(PhoneNumber("86", "13452522256")) } + it should "parse numbers with extensions in different formats" in { + // format: off + val numbers = List( + "+1 800 525 22 25 x23", + "+18005252225 ext. 23", + "+18005252225,23" + ) + // format: on + + val parsed = numbers.flatMap(PhoneNumber.parse) + + parsed should have size numbers.size + parsed should contain only PhoneNumber("1", "8005252225", Some("23")) + } + it should "return None on numbers that are shorter than the minimum number of digits for the country (i.e. US - 10, AR - 11)" in { withClue("US and CN numbers are 10 digits - 9 digit (and shorter) numbers should not fit") { // format: off @@ -76,4 +91,27 @@ class PhoneNumberTest extends FlatSpec with Matchers { List(PhoneNumber("45", "27452522"), PhoneNumber("86", "13452522256")) } + "PhoneNumber.toCompactString/toE164String" should "produce phone number in international format without whitespaces" in { + PhoneNumber.parse("+1 800 5252225").get.toCompactString shouldBe "+18005252225" + PhoneNumber.parse("+1 800 5252225").get.toE164String shouldBe "+18005252225" + + PhoneNumber.parse("+1 800 5252225 x23").get.toCompactString shouldBe "+18005252225;ext=23" + PhoneNumber.parse("+1 800 5252225 x23").get.toE164String shouldBe "+18005252225;ext=23" + } + + "PhoneNumber.toHumanReadableString" should "produce nice readable result for different countries" in { + PhoneNumber.parse("+14154234567").get.toHumanReadableString shouldBe "+1 415-423-4567" + PhoneNumber.parse("+14154234567,23").get.toHumanReadableString shouldBe "+1 415-423-4567 ext. 23" + + PhoneNumber.parse("+78005252225").get.toHumanReadableString shouldBe "+7 800 525-22-25" + + PhoneNumber.parse("+41219437898").get.toHumanReadableString shouldBe "+41 21 943 78 98" + } + + it should "throw an IllegalArgumentException if the PhoneNumber object is not parsable/valid" in { + intercept[IllegalStateException] { + PhoneNumber("+123", "1238123120938120938").toHumanReadableString + }.getMessage should include("+123 1238123120938120938 is not a valid number") + } + } diff --git a/src/test/scala/xyz/driver/core/TimeTest.scala b/core-types/src/test/scala/xyz/driver/core/TimeTest.scala index 1019f60..1019f60 100644 --- a/src/test/scala/xyz/driver/core/TimeTest.scala +++ b/core-types/src/test/scala/xyz/driver/core/TimeTest.scala diff --git a/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala b/core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala index 14dfaf9..14dfaf9 100644 --- a/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala +++ b/core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala diff --git a/src/main/scala/xyz/driver/core/Refresh.scala b/core-util/src/main/scala/xyz/driver/core/Refresh.scala index 6db9c26..6db9c26 100644 --- a/src/main/scala/xyz/driver/core/Refresh.scala +++ b/core-util/src/main/scala/xyz/driver/core/Refresh.scala diff --git a/documentation/components.dot b/documentation/components.dot new file mode 100644 index 0000000..f00dff3 --- /dev/null +++ b/documentation/components.dot @@ -0,0 +1,21 @@ +digraph core { + + rankdir = BT; + + "core-reporting"; + "core-storage" -> "core-reporting"; + "core-messaging" -> "core-reporting"; + "core-database" -> "core-reporting"; + "core-database" -> "core-types"; + "core-rest" -> "core-reporting"; + "core-rest" -> "core-types"; + "core-init" -> "core-storage"; + "core-init" -> "core-messaging"; + "core-init" -> "core-database"; + "core-init" -> "core-rest"; + "core-init" -> "core-reporting"; + + core [color=red]; + core -> "core-init" [color=red]; + +} diff --git a/documentation/components.svg b/documentation/components.svg new file mode 100644 index 0000000..0531399 --- /dev/null +++ b/documentation/components.svg @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.40.1 (20161225.0304) + --> +<!-- Title: core Pages: 1 --> +<svg width="542pt" height="260pt" + viewBox="0.00 0.00 542.09 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)"> +<title>core</title> +<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-256 538.09,-256 538.09,4 -4,4"/> +<!-- core-reporting --> +<g id="node1" class="node"> +<title>core-reporting</title> +<ellipse fill="none" stroke="#000000" cx="285.9452" cy="-234" rx="61.1893" ry="18"/> +<text text-anchor="middle" x="285.9452" y="-230.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-reporting</text> +</g> +<!-- core-storage --> +<g id="node2" class="node"> +<title>core-storage</title> +<ellipse fill="none" stroke="#000000" cx="53.9452" cy="-162" rx="53.8905" ry="18"/> +<text text-anchor="middle" x="53.9452" y="-158.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-storage</text> +</g> +<!-- core-storage->core-reporting --> +<g id="edge1" class="edge"> +<title>core-storage->core-reporting</title> +<path fill="none" stroke="#000000" d="M93.5638,-174.2954C132.345,-186.331 191.6297,-204.7297 234.1337,-217.9206"/> +<polygon fill="#000000" stroke="#000000" points="233.155,-221.2815 243.7431,-220.9028 235.2299,-214.596 233.155,-221.2815"/> +</g> +<!-- core-messaging --> +<g id="node3" class="node"> +<title>core-messaging</title> +<ellipse fill="none" stroke="#000000" cx="191.9452" cy="-162" rx="66.0889" ry="18"/> +<text text-anchor="middle" x="191.9452" y="-158.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-messaging</text> +</g> +<!-- core-messaging->core-reporting --> +<g id="edge2" class="edge"> +<title>core-messaging->core-reporting</title> +<path fill="none" stroke="#000000" d="M214.2219,-179.063C226.6308,-188.5677 242.254,-200.5344 255.7488,-210.8708"/> +<polygon fill="#000000" stroke="#000000" points="253.7613,-213.7573 263.8284,-217.0595 258.0179,-208.2001 253.7613,-213.7573"/> +</g> +<!-- core-database --> +<g id="node4" class="node"> +<title>core-database</title> +<ellipse fill="none" stroke="#000000" cx="474.9452" cy="-162" rx="59.2899" ry="18"/> +<text text-anchor="middle" x="474.9452" y="-158.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-database</text> +</g> +<!-- core-database->core-reporting --> +<g id="edge3" class="edge"> +<title>core-database->core-reporting</title> +<path fill="none" stroke="#000000" d="M437.6049,-176.2249C407.6744,-187.627 365.4902,-203.6971 333.2074,-215.9954"/> +<polygon fill="#000000" stroke="#000000" points="331.6298,-212.8509 323.5309,-219.6816 334.1218,-219.3923 331.6298,-212.8509"/> +</g> +<!-- core-types --> +<g id="node5" class="node"> +<title>core-types</title> +<ellipse fill="none" stroke="#000000" cx="443.9452" cy="-234" rx="47.3916" ry="18"/> +<text text-anchor="middle" x="443.9452" y="-230.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-types</text> +</g> +<!-- core-database->core-types --> +<g id="edge4" class="edge"> +<title>core-database->core-types</title> +<path fill="none" stroke="#000000" d="M467.1226,-180.1686C463.6452,-188.2453 459.478,-197.9239 455.642,-206.8332"/> +<polygon fill="#000000" stroke="#000000" points="452.3979,-205.5176 451.6579,-216.0866 458.8273,-208.2859 452.3979,-205.5176"/> +</g> +<!-- core-rest --> +<g id="node6" class="node"> +<title>core-rest</title> +<ellipse fill="none" stroke="#000000" cx="355.9452" cy="-162" rx="41.6928" ry="18"/> +<text text-anchor="middle" x="355.9452" y="-158.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-rest</text> +</g> +<!-- core-rest->core-reporting --> +<g id="edge5" class="edge"> +<title>core-rest->core-reporting</title> +<path fill="none" stroke="#000000" d="M339.7099,-178.6992C330.9557,-187.7035 319.978,-198.9948 310.2283,-209.0231"/> +<polygon fill="#000000" stroke="#000000" points="307.5305,-206.7771 303.0691,-216.3868 312.5494,-211.6566 307.5305,-206.7771"/> +</g> +<!-- core-rest->core-types --> +<g id="edge6" class="edge"> +<title>core-rest->core-types</title> +<path fill="none" stroke="#000000" d="M375.4753,-177.9791C387.3842,-187.7228 402.7997,-200.3355 415.9875,-211.1255"/> +<polygon fill="#000000" stroke="#000000" points="413.9037,-213.9428 423.8597,-217.5664 418.3364,-208.5251 413.9037,-213.9428"/> +</g> +<!-- core-init --> +<g id="node7" class="node"> +<title>core-init</title> +<ellipse fill="none" stroke="#000000" cx="285.9452" cy="-90" rx="40.0939" ry="18"/> +<text text-anchor="middle" x="285.9452" y="-86.3" font-family="Times,serif" font-size="14.00" fill="#000000">core-init</text> +</g> +<!-- core-init->core-reporting --> +<g id="edge11" class="edge"> +<title>core-init->core-reporting</title> +<path fill="none" stroke="#000000" d="M285.9452,-108.2377C285.9452,-132.799 285.9452,-176.7526 285.9452,-205.6459"/> +<polygon fill="#000000" stroke="#000000" points="282.4453,-205.9103 285.9452,-215.9104 289.4453,-205.9104 282.4453,-205.9103"/> +</g> +<!-- core-init->core-storage --> +<g id="edge7" class="edge"> +<title>core-init->core-storage</title> +<path fill="none" stroke="#000000" d="M252.7053,-100.3158C213.6013,-112.4515 148.3163,-132.7124 103.2985,-146.6834"/> +<polygon fill="#000000" stroke="#000000" points="102.2479,-143.3448 93.7347,-149.6515 104.3228,-150.0302 102.2479,-143.3448"/> +</g> +<!-- core-init->core-messaging --> +<g id="edge8" class="edge"> +<title>core-init->core-messaging</title> +<path fill="none" stroke="#000000" d="M265.5486,-105.6229C252.9101,-115.3035 236.4447,-127.9153 222.2928,-138.7551"/> +<polygon fill="#000000" stroke="#000000" points="220.0846,-136.0377 214.274,-144.8971 224.3411,-141.5948 220.0846,-136.0377"/> +</g> +<!-- core-init->core-database --> +<g id="edge9" class="edge"> +<title>core-init->core-database</title> +<path fill="none" stroke="#000000" d="M316.732,-101.7283C347.0407,-113.2745 393.7166,-131.0558 428.5269,-144.3169"/> +<polygon fill="#000000" stroke="#000000" points="427.4162,-147.639 438.0071,-147.9284 429.9083,-141.0976 427.4162,-147.639"/> +</g> +<!-- core-init->core-rest --> +<g id="edge10" class="edge"> +<title>core-init->core-rest</title> +<path fill="none" stroke="#000000" d="M302.1806,-106.6992C311.2805,-116.0592 322.7832,-127.8905 332.8116,-138.2055"/> +<polygon fill="#000000" stroke="#000000" points="330.3176,-140.6611 339.7979,-145.3913 335.3366,-135.7816 330.3176,-140.6611"/> +</g> +<!-- core --> +<g id="node8" class="node"> +<title>core</title> +<ellipse fill="none" stroke="#ff0000" cx="285.9452" cy="-18" rx="27" ry="18"/> +<text text-anchor="middle" x="285.9452" y="-14.3" font-family="Times,serif" font-size="14.00" fill="#000000">core</text> +</g> +<!-- core->core-init --> +<g id="edge12" class="edge"> +<title>core->core-init</title> +<path fill="none" stroke="#ff0000" d="M285.9452,-36.1686C285.9452,-43.869 285.9452,-53.0257 285.9452,-61.5834"/> +<polygon fill="#ff0000" stroke="#ff0000" points="282.4453,-61.5867 285.9452,-71.5867 289.4453,-61.5868 282.4453,-61.5867"/> +</g> +</g> +</svg> diff --git a/documentation/example/.gitignore b/documentation/example/.gitignore new file mode 100644 index 0000000..2157ef4 --- /dev/null +++ b/documentation/example/.gitignore @@ -0,0 +1 @@ +.data-* diff --git a/documentation/example/build.sbt b/documentation/example/build.sbt new file mode 100644 index 0000000..d2bd592 --- /dev/null +++ b/documentation/example/build.sbt @@ -0,0 +1,6 @@ +lazy val `example-app` = project + .in(file(".")) + .enablePlugins(ServicePlugin) + .settings( + libraryDependencies += "xyz.driver" %% "core-init" % "2.0.0-M5" + ) diff --git a/documentation/example/project/build.properties b/documentation/example/project/build.properties new file mode 100644 index 0000000..0cd8b07 --- /dev/null +++ b/documentation/example/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.3 diff --git a/documentation/example/project/plugins.sbt b/documentation/example/project/plugins.sbt new file mode 100644 index 0000000..53be881 --- /dev/null +++ b/documentation/example/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("xyz.driver" % "sbt-settings" % "2.0.12") diff --git a/documentation/example/src/main/scala/example/Main.scala b/documentation/example/src/main/scala/example/Main.scala new file mode 100644 index 0000000..2548f79 --- /dev/null +++ b/documentation/example/src/main/scala/example/Main.scala @@ -0,0 +1,21 @@ +package example + +import xyz.driver.core.init + +object Main extends init.SimpleHttpApp { + + lazy val fs = this.storage("data") + + override def applicationRoute = path("data" / Segment) { key => + post { + entity(as[Array[Byte]]) { value => + complete(fs.uploadContent(key, value)) + } + } ~ get { + rejectEmptyResponse{ + complete(fs.content(key)) + } + } + } + +} diff --git a/project/MiMaSettings.scala b/project/MiMaSettings.scala new file mode 100644 index 0000000..d3ec3c1 --- /dev/null +++ b/project/MiMaSettings.scala @@ -0,0 +1,28 @@ +import com.typesafe.tools.mima.plugin.MimaPlugin +import sbt.{Def, _} +import sbt.Keys._ + +/** This plugin extends the Migration Manager (MiMa) Plugin with common settings + * for driver-core projects. + */ +object MiMaSettings extends AutoPlugin { + + override def requires = MimaPlugin + override def trigger = allRequirements + + object autoImport { + val abiVersion = settingKey[String]("Previous version of binary-compatible projects") + val checkAbi = taskKey[Unit]("Check ABI compatibility with declared abiVersion") + } + import autoImport._ + import MimaPlugin.autoImport._ + + override def buildSettings: Seq[Def.Setting[_]] = Seq(abiVersion := "") + override def projectSettings: Seq[Def.Setting[_]] = Seq( + mimaPreviousArtifacts := Set( + "xyz.driver" %% name.value % abiVersion.value + ), + checkAbi := mimaReportBinaryIssues.value + ) + +} diff --git a/project/build.properties b/project/build.properties index 5620cc5..0cd8b07 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.1 +sbt.version=1.2.3 diff --git a/project/plugins.sbt b/project/plugins.sbt index 17a573c..f0e3a6d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,2 @@ -addSbtPlugin("xyz.driver" % "sbt-settings" % "2.0.7") +addSbtPlugin("xyz.driver" % "sbt-settings" % "2.0.9") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.3.0") diff --git a/src/main/scala/xyz/driver/core/database/MdcAsyncExecutor.scala b/src/main/scala/xyz/driver/core/database/MdcAsyncExecutor.scala deleted file mode 100644 index 5939efb..0000000 --- a/src/main/scala/xyz/driver/core/database/MdcAsyncExecutor.scala +++ /dev/null @@ -1,53 +0,0 @@ -/** Code ported from "de.geekonaut" %% "slickmdc" % "1.0.0" - * License: @see https://github.com/AVGP/slickmdc/blob/master/LICENSE - * Blog post: @see http://50linesofco.de/post/2016-07-01-slick-and-slf4j-mdc-logging-in-scala.html - */ -package xyz.driver.core -package database - -import java.util.concurrent._ -import java.util.concurrent.atomic.AtomicInteger - -import scala.concurrent._ -import com.typesafe.scalalogging.StrictLogging -import slick.util.AsyncExecutor - -import logging.MdcExecutionContext - -/** Taken from the original Slick AsyncExecutor and simplified - * @see https://github.com/slick/slick/blob/3.1/slick/src/main/scala/slick/util/AsyncExecutor.scala - */ -object MdcAsyncExecutor extends StrictLogging { - - /** Create an AsyncExecutor with a fixed-size thread pool. - * - * @param name The name for the thread pool. - * @param numThreads The number of threads in the pool. - */ - def apply(name: String, numThreads: Int): AsyncExecutor = { - new AsyncExecutor { - val tf = new DaemonThreadFactory(name + "-") - - lazy val executionContext = { - new MdcExecutionContext(ExecutionContext.fromExecutor(Executors.newFixedThreadPool(numThreads, tf))) - } - - def close(): Unit = {} - } - } - - def default(name: String = "AsyncExecutor.default"): AsyncExecutor = apply(name, 20) - - private class DaemonThreadFactory(namePrefix: String) extends ThreadFactory { - private[this] val group = - Option(System.getSecurityManager).fold(Thread.currentThread.getThreadGroup)(_.getThreadGroup) - private[this] val threadNumber = new AtomicInteger(1) - - def newThread(r: Runnable): Thread = { - val t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement, 0) - if (!t.isDaemon) t.setDaemon(true) - if (t.getPriority != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY) - t - } - } -} diff --git a/src/main/scala/xyz/driver/core/domain.scala b/src/main/scala/xyz/driver/core/domain.scala deleted file mode 100644 index 59bed54..0000000 --- a/src/main/scala/xyz/driver/core/domain.scala +++ /dev/null @@ -1,40 +0,0 @@ -package xyz.driver.core - -import com.google.i18n.phonenumbers.PhoneNumberUtil -import scalaz.Equal -import scalaz.std.string._ -import scalaz.syntax.equal._ - -object domain { - - final case class Email(username: String, domain: String) { - override def toString: String = username + "@" + domain - } - - object Email { - implicit val emailEqual: Equal[Email] = Equal.equal { - case (left, right) => left.toString.toLowerCase === right.toString.toLowerCase - } - - def parse(emailString: String): Option[Email] = { - Some(emailString.split("@")) collect { - case Array(username, domain) => Email(username, domain) - } - } - } - - final case class PhoneNumber(countryCode: String = "1", number: String) { - override def toString: String = s"+$countryCode $number" - } - - object PhoneNumber { - - private val phoneUtil = PhoneNumberUtil.getInstance() - - def parse(phoneNumber: String): Option[PhoneNumber] = { - val validated = - util.Try(phoneUtil.parseAndKeepRawInput(phoneNumber, "US")).toOption.filter(phoneUtil.isValidNumber) - validated.map(pn => PhoneNumber(pn.getCountryCode.toString, pn.getNationalNumber.toString)) - } - } -} diff --git a/src/main/scala/xyz/driver/core/init/Platform.scala b/src/main/scala/xyz/driver/core/init/Platform.scala deleted file mode 100644 index 2daa2c8..0000000 --- a/src/main/scala/xyz/driver/core/init/Platform.scala +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.driver.core -package init - -import java.nio.file.{Files, Path, Paths} - -import com.google.auth.oauth2.ServiceAccountCredentials - -sealed trait Platform -object Platform { - case class GoogleCloud(keyfile: Path, namespace: String) extends Platform { - def credentials: ServiceAccountCredentials = ServiceAccountCredentials.fromStream( - Files.newInputStream(keyfile) - ) - def project: String = credentials.getProjectId - } - // case object AliCloud extends Platform - case object Dev extends Platform - - lazy val fromEnv: Platform = { - def isGoogle = sys.env.get("GOOGLE_APPLICATION_CREDENTIALS").map { value => - val keyfile = Paths.get(value) - require(Files.isReadable(keyfile), s"Google credentials file $value is not readable.") - val namespace = sys.env.getOrElse("SERVICE_NAMESPACE", sys.error("Namespace not set")) - GoogleCloud(keyfile, namespace) - } - isGoogle.getOrElse(Dev) - } - - def current: Platform = fromEnv - -} diff --git a/src/test/scala/xyz/driver/core/rest/DriverAppTest.scala b/src/test/scala/xyz/driver/core/DriverAppTest.scala index 324c8d8..986e5d3 100644 --- a/src/test/scala/xyz/driver/core/rest/DriverAppTest.scala +++ b/src/test/scala/xyz/driver/core/DriverAppTest.scala @@ -1,4 +1,4 @@ -package xyz.driver.core.rest +package xyz.driver.core import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.model.{HttpMethod, StatusCodes} |