Открыть боковую панель
Aurora OS
Kotlin Multiplatform
Libraries
ktor
Коммиты
a8132bde
Коммит
a8132bde
создал
Май 18, 2022
по автору
rsinukov
Зафиксировано автором
Rustam
Июн 30, 2022
Просмотр файлов
KTOR-503 Add request validation plugin
владелец
c7823f18
Изменения
6
Скрыть пробелы
Построчно
Рядом
ktor-server/ktor-server-plugins/ktor-server-request-validation/api/ktor-server-request-validation.api
0 → 100644
Просмотр файла @
a8132bde
public final class io/ktor/server/plugins/requestvalidation/RequestValidationConfig {
public fun <init> ()V
public final fun validate (Lio/ktor/server/plugins/requestvalidation/Validator;)V
public final fun validate (Lkotlin/jvm/functions/Function1;)V
public final fun validate (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)V
}
public final class io/ktor/server/plugins/requestvalidation/RequestValidationConfig$ValidatorBuilder {
public fun <init> ()V
public final fun filter (Lkotlin/jvm/functions/Function1;)V
public final fun validation (Lkotlin/jvm/functions/Function2;)V
}
public final class io/ktor/server/plugins/requestvalidation/RequestValidationException : java/lang/IllegalArgumentException {
public fun <init> (Ljava/lang/Object;Ljava/util/List;)V
public final fun getReasons ()Ljava/util/List;
public final fun getValue ()Ljava/lang/Object;
}
public final class io/ktor/server/plugins/requestvalidation/RequestValidationKt {
public static final fun getRequestValidation ()Lio/ktor/server/application/RouteScopedPlugin;
}
public abstract class io/ktor/server/plugins/requestvalidation/ValidationResult {
}
public final class io/ktor/server/plugins/requestvalidation/ValidationResult$Invalid : io/ktor/server/plugins/requestvalidation/ValidationResult {
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/util/List;)V
public final fun getReasons ()Ljava/util/List;
}
public final class io/ktor/server/plugins/requestvalidation/ValidationResult$Valid : io/ktor/server/plugins/requestvalidation/ValidationResult {
public static final field INSTANCE Lio/ktor/server/plugins/requestvalidation/ValidationResult$Valid;
}
public abstract interface class io/ktor/server/plugins/requestvalidation/Validator {
public abstract fun filter (Ljava/lang/Object;)Z
public abstract fun validate (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
ktor-server/ktor-server-plugins/ktor-server-request-validation/build.gradle.kts
0 → 100644
Просмотр файла @
a8132bde
/*
* Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
description
=
""
kotlin
{
sourceSets
{
jvmAndNixTest
{
dependencies
{
implementation
(
project
(
":ktor-server:ktor-server-plugins:ktor-server-status-pages"
))
}
}
}
}
ktor-server/ktor-server-plugins/ktor-server-request-validation/jvmAndNix/src/io/ktor/server/plugins/requestvalidation/RequestValidation.kt
0 → 100644
Просмотр файла @
a8132bde
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package
io.ktor.server.plugins.requestvalidation
import
io.ktor.server.application.*
import
io.ktor.server.request.*
/**
* A result of validation.
*/
public
sealed
class
ValidationResult
{
/**
* Represents successful result of validation
*/
public
object
Valid
:
ValidationResult
()
/**
* Represents not successful result of validation
*/
public
class
Invalid
(
public
val
reasons
:
List
<
String
>)
:
ValidationResult
()
{
public
constructor
(
reason
:
String
)
:
this
(
listOf
(
reason
))
}
}
/**
* A validator that should be registered with [RequestValidation] plugin
*/
public
interface
Validator
{
/**
* Validates [value]
*/
public
suspend
fun
validate
(
value
:
Any
):
ValidationResult
/**
* Check if [value] should be checked by this validator
*/
public
fun
filter
(
value
:
Any
):
Boolean
}
/**
* A plugin that checks request body using [Validator]
* Example:
* ```
* install(RequestValidation) {
* validate<String> {
* if (!it.startsWith("+")) ValidationResult.Invalid("$it should start with \"+\"")
* else ValidationResult.Valid
* }
* }
* install(StatusPages) {
* exception<RequestValidationException> { call, cause ->
* call.respond(HttpStatusCode.BadRequest, cause.reasons.joinToString())
* }
* }
* ```
*/
public
val
RequestValidation
:
RouteScopedPlugin
<
RequestValidationConfig
>
=
createRouteScopedPlugin
(
"RequestValidation"
,
::
RequestValidationConfig
)
{
val
validators
=
pluginConfig
.
validators
on
(
RequestBodyTransformed
)
{
content
->
@Suppress
(
"UNCHECKED_CAST"
)
val
failures
=
validators
.
filter
{
it
.
filter
(
content
)
}
.
map
{
it
.
validate
(
content
)
}
.
filterIsInstance
<
ValidationResult
.
Invalid
>()
if
(
failures
.
isNotEmpty
())
{
throw
RequestValidationException
(
content
,
failures
.
flatMap
{
it
.
reasons
})
}
}
}
/**
* Thrown when validation fails.
* @property value - invalid request body
* @property reasons - combined reasons of all validation failures for this request
*/
public
class
RequestValidationException
(
public
val
value
:
Any
,
public
val
reasons
:
List
<
String
>)
:
IllegalArgumentException
(
"Validation failed for $value. Reasons: ${reasons.joinToString("
.
")}"
)
private
object
RequestBodyTransformed
:
Hook
<
suspend
(content
:
Any
)
-
>
Unit
>
{
override
fun
install
(
pipeline
:
ApplicationCallPipeline
,
handler
:
suspend
(
content
:
Any
)
->
Unit
)
{
pipeline
.
receivePipeline
.
intercept
(
ApplicationReceivePipeline
.
After
)
{
handler
(
subject
)
}
}
}
ktor-server/ktor-server-plugins/ktor-server-request-validation/jvmAndNix/src/io/ktor/server/plugins/requestvalidation/RequestValidationConfig.kt
0 → 100644
Просмотр файла @
a8132bde
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package
io.ktor.server.plugins.requestvalidation
import
kotlin.reflect.*
/**
* A config for [RequestValidation] plugin
*/
public
class
RequestValidationConfig
{
internal
val
validators
:
MutableList
<
Validator
>
=
mutableListOf
()
/**
* Registers [validator]
*/
public
fun
validate
(
validator
:
Validator
)
{
validators
.
add
(
validator
)
}
/**
* Registers [Validator] that should check instances of a [kClass] using [block]
*/
public
fun
<
T
:
Any
>
validate
(
kClass
:
KClass
<
T
>,
block
:
suspend
(
T
)
->
ValidationResult
)
{
val
validator
=
object
:
Validator
{
@Suppress
(
"UNCHECKED_CAST"
)
override
suspend
fun
validate
(
value
:
Any
):
ValidationResult
=
block
(
value
as
T
)
override
fun
filter
(
value
:
Any
):
Boolean
=
kClass
.
isInstance
(
value
)
}
validate
(
validator
)
}
/**
* Registers [Validator] that should check instances of a [T] using [block]
*/
public
inline
fun
<
reified
T
:
Any
>
validate
(
noinline
block
:
suspend
(
T
)
->
ValidationResult
)
{
validate
(
T
::
class
,
block
)
}
/**
* Registers [Validator] using DSL
* ```
* validate {
* filter { it is Int }
* validation { check(it is Int); ... }
* }
* ```
*/
public
fun
validate
(
block
:
ValidatorBuilder
.()
->
Unit
)
{
val
builder
=
ValidatorBuilder
().
apply
(
block
)
validate
(
builder
.
build
())
}
public
class
ValidatorBuilder
{
private
lateinit
var
validationBlock
:
suspend
(
Any
)
->
ValidationResult
private
lateinit
var
filterBlock
:
(
Any
)
->
Boolean
public
fun
filter
(
block
:
(
Any
)
->
Boolean
)
{
filterBlock
=
block
}
public
fun
validation
(
block
:
suspend
(
Any
)
->
ValidationResult
)
{
validationBlock
=
block
}
internal
fun
build
():
Validator
{
check
(
::
validationBlock
.
isInitialized
)
{
"`validation { ... } block is not ser`"
}
check
(
::
filterBlock
.
isInitialized
)
{
"`filter { ... } block is not set`"
}
return
object
:
Validator
{
override
suspend
fun
validate
(
value
:
Any
)
=
validationBlock
(
value
)
override
fun
filter
(
value
:
Any
):
Boolean
=
filterBlock
(
value
)
}
}
}
}
ktor-server/ktor-server-plugins/ktor-server-request-validation/jvmAndNix/test/io/ktor/server/plugins/requestvalidation/RequestValidationTest.kt
0 → 100644
Просмотр файла @
a8132bde
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package
io.ktor.server.plugins.requestvalidation
import
io.ktor.client.request.*
import
io.ktor.client.statement.*
import
io.ktor.http.*
import
io.ktor.server.application.*
import
io.ktor.server.plugins.statuspages.*
import
io.ktor.server.request.*
import
io.ktor.server.response.*
import
io.ktor.server.routing.*
import
io.ktor.server.testing.*
import
io.ktor.utils.io.*
import
io.ktor.utils.io.core.*
import
kotlin.test.*
class
RequestValidationTest
{
@Test
fun
testSimpleValidationByClass
()
=
testApplication
{
install
(
RequestValidation
)
{
validate
<
CharSequence
>
{
if
(!
it
.
startsWith
(
"+"
))
ValidationResult
.
Invalid
(
listOf
(
"$it should start with \"+\""
))
else
ValidationResult
.
Valid
}
validate
<
String
>
{
if
(!
it
.
endsWith
(
"!"
))
ValidationResult
.
Invalid
(
listOf
(
"$it should end with \"!\""
))
else
ValidationResult
.
Valid
}
}
install
(
StatusPages
)
{
exception
<
RequestValidationException
>
{
call
,
cause
->
call
.
respond
(
HttpStatusCode
.
BadRequest
,
"${cause.value}\n${cause.reasons.joinToString()}"
)
}
}
routing
{
get
(
"/text"
)
{
val
body
=
call
.
receive
<
String
>()
call
.
respond
(
body
)
}
get
(
"/channel"
)
{
call
.
receive
<
ByteReadChannel
>().
discard
()
call
.
respond
(
"OK"
)
}
}
client
.
get
(
"/text"
)
{
setBody
(
"1"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
BadRequest
,
it
.
status
)
assertEquals
(
"1\n1 should start with \"+\", 1 should end with \"!\""
,
body
)
}
client
.
get
(
"/text"
)
{
setBody
(
"+1"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
BadRequest
,
it
.
status
)
assertEquals
(
"+1\n+1 should end with \"!\""
,
body
)
}
client
.
get
(
"/text"
)
{
setBody
(
"1!"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
BadRequest
,
it
.
status
)
assertEquals
(
"1!\n1! should start with \"+\""
,
body
)
}
client
.
get
(
"/text"
)
{
setBody
(
"+1!"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
OK
,
it
.
status
)
assertEquals
(
"+1!"
,
body
)
}
client
.
get
(
"/channel"
)
{
setBody
(
"1"
)
}.
let
{
assertEquals
(
HttpStatusCode
.
OK
,
it
.
status
)
}
}
@Test
fun
testValidatorDsl
()
=
testApplication
{
install
(
RequestValidation
)
{
validate
{
filter
{
it
is
ByteArray
}
validation
{
check
(
it
is
ByteArray
)
val
intValue
=
String
(
it
).
toInt
()
if
(
intValue
<
0
)
ValidationResult
.
Invalid
(
"Value is negative"
)
else
ValidationResult
.
Valid
}
}
}
install
(
StatusPages
)
{
exception
<
RequestValidationException
>
{
call
,
cause
->
call
.
respond
(
HttpStatusCode
.
BadRequest
,
"${cause.value}\n${cause.reasons.joinToString()}"
)
}
}
routing
{
get
(
"/text"
)
{
val
body
=
call
.
receive
<
String
>()
call
.
respond
(
body
)
}
get
(
"/array"
)
{
val
body
=
call
.
receive
<
ByteArray
>()
call
.
respond
(
String
(
body
))
}
}
client
.
get
(
"/text"
)
{
setBody
(
"1"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
OK
,
it
.
status
)
assertEquals
(
"1"
,
body
)
}
client
.
get
(
"/array"
)
{
setBody
(
"1"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
OK
,
it
.
status
)
assertEquals
(
"1"
,
body
)
}
client
.
get
(
"/array"
)
{
setBody
(
"-1"
)
}.
let
{
val
body
=
it
.
bodyAsText
()
assertEquals
(
HttpStatusCode
.
BadRequest
,
it
.
status
)
assertTrue
(
body
.
endsWith
(
"Value is negative"
),
body
)
}
}
}
settings.gradle.kts
Просмотр файла @
a8132bde
...
...
@@ -115,6 +115,7 @@ include(":ktor-server:ktor-server-plugins:ktor-server-metrics-micrometer")
include
(
":ktor-server:ktor-server-plugins:ktor-server-mustache"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-partial-content"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-pebble"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-request-validation"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-resources"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-sessions"
)
include
(
":ktor-server:ktor-server-plugins:ktor-server-status-pages"
)
...
...
Редактирование
Предварительный просмотр
Поддерживает Markdown
0%
Попробовать снова
или
прикрепить новый файл
.
Отмена
You are about to add
0
people
to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Отмена
Пожалуйста,
зарегистрируйтесь
или
войдите
чтобы прокомментировать