Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
services
t3o sites
extensions.typo3.org
extensions.typo3.org
Commits
fd47b158
Commit
fd47b158
authored
Sep 29, 2020
by
Oliver Bartsch
Browse files
Replace RequiredValidator with checks in RouteResolver
parent
a3ef3bdf
Changes
15
Hide whitespace changes
Inline
Side-by-side
extensions/ter/Classes/Controller/Api/AbstractApiController.php
View file @
fd47b158
...
...
@@ -84,7 +84,7 @@ abstract class AbstractApiController implements RequestHandlerInterface
}
}
if
(
!
$routeArgument
->
isValid
())
{
$invalidArguments
[]
=
$routeArgument
;
$invalidArguments
[
$routeArgument
->
getName
()
]
=
$routeArgument
;
}
}
...
...
extensions/ter/Classes/Exception/RequiredArgumentMissingException.php
0 → 100644
View file @
fd47b158
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Exception
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
class
RequiredArgumentMissingException
extends
Exception
{
}
extensions/ter/Classes/Middleware/RestRouteDispatcher.php
View file @
fd47b158
...
...
@@ -71,6 +71,7 @@ final class RestRouteDispatcher implements MiddlewareInterface
$this
->
routeConfiguration
->
setBase
(
self
::
API_REQUEST_PATH
.
$version
)
->
setPath
(
str_replace
(
self
::
API_REQUEST_PATH
.
$version
,
''
,
$path
))
->
setSchema
(
$schema
)
->
createRouteCollection
();
...
...
extensions/ter/Classes/Rest/RouteArgument/AbstractRouteArgument.php
View file @
fd47b158
...
...
@@ -12,7 +12,6 @@ namespace T3o\Ter\Rest\RouteArgument;
* of the License, or any later version.
*/
use
T3o\Ter\Rest\Validaton\RequiredValidator
;
use
T3o\Ter\Rest\Validaton\ValidationErrorInterface
;
use
TYPO3\CMS\Core\Utility\GeneralUtility
;
...
...
@@ -30,10 +29,6 @@ abstract class AbstractRouteArgument implements RouteArgumentInterface, \JsonSer
{
$this
->
name
=
$name
;
$this
->
configuration
=
$configuration
;
if
((
bool
)(
$this
->
configuration
[
'required'
]
??
false
))
{
$this
->
validators
[
RequiredValidator
::
class
]
=
[];
}
}
public
function
getName
():
string
...
...
extensions/ter/Classes/Rest/RouteArgument/IntegerArgument.php
View file @
fd47b158
...
...
@@ -15,6 +15,7 @@ namespace T3o\Ter\Rest\RouteArgument;
use
T3o\Ter\Rest\Validaton\EnumValidator
;
use
T3o\Ter\Rest\Validaton\MaxValidator
;
use
T3o\Ter\Rest\Validaton\MinValidator
;
use
TYPO3\CMS\Core\Utility\MathUtility
;
/**
* Implementation for an integer argument
...
...
@@ -23,10 +24,18 @@ class IntegerArgument extends AbstractRouteArgument
{
protected
int
$value
;
public
function
__construct
(
string
$name
,
array
$configuration
,
int
$value
)
public
function
__construct
(
string
$name
,
array
$configuration
,
$value
)
{
if
(
!
is_int
(
$value
)
&&
!
MathUtility
::
canBeInterpretedAsInteger
(
$value
))
{
throw
new
\
InvalidArgumentException
(
sprintf
(
'Value \'%s\' for argument \'%s\' must be of type integer.'
,
$value
,
$name
),
1601313518
);
}
parent
::
__construct
(
$name
,
$configuration
);
$this
->
value
=
$value
;
$this
->
value
=
(
int
)
$value
;
$this
->
addValidatorsFromConfiguration
();
}
...
...
extensions/ter/Classes/Rest/RouteArgument/ObjectArgument.php
View file @
fd47b158
...
...
@@ -23,9 +23,17 @@ class ObjectArgument extends AbstractRouteArgument implements DeepObjectRouteArg
protected
array
$value
;
protected
array
$properties
;
public
function
__construct
(
string
$name
,
array
$configuration
,
array
$value
)
public
function
__construct
(
string
$name
,
array
$configuration
,
$value
)
{
if
(
!
is_array
(
$value
))
{
throw
new
\
InvalidArgumentException
(
sprintf
(
'Value \'%s\' for argument \'%s\' must be of type array.'
,
$value
,
$name
),
1601313412
);
}
parent
::
__construct
(
$name
,
$configuration
);
$this
->
value
=
$value
;
$routeArgumentFactory
=
GeneralUtility
::
makeInstance
(
RouteArgumentFactory
::
class
,
...
...
extensions/ter/Classes/Rest/RouteArgument/RouteArgumentFactory.php
View file @
fd47b158
...
...
@@ -29,7 +29,7 @@ final class RouteArgumentFactory
public
function
create
(
string
$name
,
array
$configuration
,
$value
):
RouteArgumentInterface
{
$type
=
ucfirst
(
$configuration
[
'schema'
][
'type'
]
??
''
);
$type
=
ucfirst
(
(
string
)(
$configuration
[
'schema'
][
'type'
]
??
''
)
)
;
if
(
$type
===
''
)
{
throw
new
\
InvalidArgumentException
(
sprintf
(
'RouteArgument type cannot be empty for argument: %s'
,
$name
),
1601024733
);
...
...
extensions/ter/Classes/Rest/RouteArgument/StringArgument.php
View file @
fd47b158
...
...
@@ -24,9 +24,17 @@ class StringArgument extends AbstractRouteArgument
{
protected
string
$value
;
public
function
__construct
(
string
$name
,
array
$configuration
,
string
$value
)
public
function
__construct
(
string
$name
,
array
$configuration
,
$value
)
{
if
(
!
is_string
(
$value
))
{
throw
new
\
InvalidArgumentException
(
sprintf
(
'Value \'%s\' for argument \'%s\' must be of type integer.'
,
$value
,
$name
),
1601313573
);
}
parent
::
__construct
(
$name
,
$configuration
);
$this
->
value
=
$value
;
$this
->
addValidatorsFromConfiguration
();
}
...
...
extensions/ter/Classes/Rest/RouteConfiguration.php
View file @
fd47b158
...
...
@@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
final
class
RouteConfiguration
implements
SingletonInterface
{
protected
string
$base
;
protected
string
$path
;
protected
array
$schema
;
protected
RouteCollection
$routeCollection
;
...
...
@@ -42,6 +43,17 @@ final class RouteConfiguration implements SingletonInterface
return
$this
->
base
;
}
public
function
setPath
(
string
$path
):
self
{
$this
->
path
=
$path
;
return
$this
;
}
public
function
getPath
():
string
{
return
$this
->
path
;
}
public
function
setSchema
(
array
$schema
):
self
{
$this
->
schema
=
$schema
;
...
...
@@ -53,6 +65,11 @@ final class RouteConfiguration implements SingletonInterface
return
$this
->
schema
;
}
public
function
getRoute
(
string
$operationId
):
?Route
{
return
$this
->
routeCollection
->
get
(
$operationId
);
}
public
function
getRouteCollection
():
RouteCollection
{
return
$this
->
routeCollection
;
...
...
@@ -90,13 +107,40 @@ final class RouteConfiguration implements SingletonInterface
return
$this
;
}
public
function
getEndpoint
(
string
$fullPath
):
string
public
function
getEndpoint
Configuration
(
string
$operationId
):
array
{
return
'/'
.
ltrim
(
str_replace
(
$this
->
base
,
''
,
$fullPath
),
'/'
);
$route
=
$this
->
getRoute
(
$operationId
);
if
(
$route
===
null
)
{
return
[];
}
$path
=
$route
->
getPath
();
$method
=
strtolower
(
$route
->
getMethods
()[
0
]
??
''
);
if
(
isset
(
$this
->
schema
[
'paths'
][
$path
][
$method
]))
{
return
(
array
)
$this
->
schema
[
'paths'
][
$path
][
$method
];
}
return
(
array
)(
$this
->
schema
[
'paths'
][
$path
]
??
[]);
}
public
function
getParameterConfiguration
(
string
$name
):
array
{
return
(
array
)(
$this
->
schema
[
'components'
][
'parameters'
][
GeneralUtility
::
underscoredToUpperCamelCase
(
$name
)]
??
[]);
}
public
function
getParameterConfigurationByReference
(
string
$reference
):
array
{
$parts
=
explode
(
'/'
,
str_replace
(
'#/'
,
''
,
$reference
));
$configuration
=
$this
->
schema
;
foreach
(
$parts
as
$part
)
{
if
(
isset
(
$configuration
[
$part
])
&&
is_array
(
$configuration
[
$part
]))
{
$configuration
=
$configuration
[
$part
];
}
}
return
array_merge
(
$configuration
,
[
'reference'
=>
end
(
$parts
)]);
}
}
extensions/ter/Classes/Rest/RouteHandler.php
View file @
fd47b158
...
...
@@ -17,6 +17,7 @@ use Psr\Http\Message\ServerRequestInterface;
use
Psr\Http\Server\RequestHandlerInterface
;
use
Symfony\Component\Routing\Exception\MethodNotAllowedException
;
use
Symfony\Component\Routing\Exception\ResourceNotFoundException
;
use
T3o\Ter\Exception\RequiredArgumentMissingException
;
use
T3o\Ter\Rest\Response\ApiResponseFactory
;
/**
...
...
@@ -55,6 +56,8 @@ final class RouteHandler implements RequestHandlerInterface
}
catch
(
MethodNotAllowedException
$e
)
{
$message
=
'The method '
.
$request
->
getMethod
()
.
' is not allowed.'
;
return
$this
->
apiResponseFactory
->
createErrorResponseForRequest
(
$request
,
1600994273
,
$message
,
405
);
}
catch
(
RequiredArgumentMissingException
$e
)
{
return
$this
->
apiResponseFactory
->
createErrorResponseForRequest
(
$request
,
1600996241
,
$e
->
getMessage
(),
400
);
}
catch
(
ResourceNotFoundException
|
\
OutOfRangeException
|
\
InvalidArgumentException
$e
)
{
return
$this
->
apiResponseFactory
->
createErrorResponseForRequest
(
$request
,
1601289397
,
$e
->
getMessage
());
}
...
...
extensions/ter/Classes/Rest/RouteResolver.php
View file @
fd47b158
...
...
@@ -16,6 +16,7 @@ use Psr\Http\Message\ServerRequestInterface;
use
Symfony\Component\Routing\Exception\ResourceNotFoundException
;
use
Symfony\Component\Routing\Matcher\UrlMatcher
;
use
Symfony\Component\Routing\RequestContext
;
use
T3o\Ter\Exception\RequiredArgumentMissingException
;
use
T3o\Ter\Rest\RouteArgument\RouteArgumentFactory
;
use
TYPO3\CMS\Core\Routing\RouteResultInterface
;
...
...
@@ -46,7 +47,7 @@ final class RouteResolver
$resultParameters
=
(
new
UrlMatcher
(
$this
->
routeConfiguration
->
getRouteCollection
(),
$this
->
getRequestContext
(
$request
)
))
->
match
(
$this
->
routeConfiguration
->
getEndpoint
(
$request
->
getUri
()
->
getPath
())
)
;
))
->
match
(
$this
->
routeConfiguration
->
getPath
());
if
(
!
(
$resultParameters
[
'_route'
]
??
false
))
{
throw
new
ResourceNotFoundException
(
'No resource found for the given route.'
,
1600994133
);
...
...
@@ -63,7 +64,7 @@ final class RouteResolver
);
$arguments
=
array_merge
(
array_filter
(
$resultParameters
,
static
function
(
$_
,
$k
){
array_filter
(
$resultParameters
,
static
function
(
$_
,
$k
)
{
return
strpos
(
$k
,
'_'
)
!==
0
;
},
ARRAY_FILTER_USE_BOTH
),
$request
->
getQueryParams
()
...
...
@@ -76,6 +77,40 @@ final class RouteResolver
}
}
$endpointConfiguration
=
$this
->
routeConfiguration
->
getEndpointConfiguration
(
$routeResultArguments
->
offsetGet
(
'operationId'
)
);
if
(
isset
(
$endpointConfiguration
[
'parameters'
])
&&
is_array
(
$endpointConfiguration
[
'parameters'
]))
{
foreach
(
$endpointConfiguration
[
'parameters'
]
as
$parameter
)
{
if
((
bool
)(
$parameter
[
'required'
]
??
false
)
&&
!
$routeResultArguments
->
hasRouteArgument
(
$parameter
[
'name'
])
)
{
throw
new
RequiredArgumentMissingException
(
sprintf
(
'%s argument %s is required but is missing in the request.'
,
ucfirst
(
$parameter
[
'in'
]),
$parameter
[
'name'
]
),
1601369655
);
}
}
}
if
(
isset
(
$endpointConfiguration
[
'requestBody'
])
&&
is_array
(
$endpointConfiguration
[
'requestBody'
]))
{
foreach
(
$endpointConfiguration
[
'requestBody'
]
as
$key
=>
$requestBody
)
{
if
(
$key
===
'$ref'
)
{
$requestBody
=
$this
->
routeConfiguration
->
getParameterConfigurationByReference
(
$requestBody
);
}
if
((
bool
)(
$requestBody
[
'required'
]
??
false
)
&&
$request
->
getParsedBody
()
===
null
)
{
throw
new
RequiredArgumentMissingException
(
sprintf
(
'Required request body for %s is missing in the request.'
,
$requestBody
[
'reference'
]),
1601369655
);
}
}
}
return
$routeResultArguments
;
}
...
...
extensions/ter/Classes/Rest/RouteResultArguments.php
View file @
fd47b158
...
...
@@ -74,7 +74,7 @@ final class RouteResultArguments implements RouteResultInterface
public
function
hasRouteArgument
(
string
$name
):
bool
{
return
$this
->
routeArguments
[
$name
]
??
false
;
return
isset
(
$this
->
routeArguments
[
$name
]
)
;
}
public
function
getRouteArgument
(
string
$name
):
RouteArgumentInterface
...
...
extensions/ter/Classes/Rest/Validaton/RequiredValidator.php
deleted
100644 → 0
View file @
a3ef3bdf
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Rest\Validaton
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
T3o\Ter\Rest\RouteArgument\IntegerArgument
;
use
T3o\Ter\Rest\RouteArgument\RouteArgumentInterface
;
use
T3o\Ter\Rest\RouteArgument\StringArgument
;
/**
* Validator implementation for OpenApi `required` property.
*
* @todo This validator may be superfluous as required arguments should be checked
* while resolving the route. Furthermore path arguments are already checked
* on url matching.
*/
final
class
RequiredValidator
implements
ValidatorInterface
{
public
function
validate
(
RouteArgumentInterface
$routeArgument
):
void
{
$class
=
get_class
(
$routeArgument
);
switch
(
$class
)
{
case
StringArgument
::
class
:
$result
=
$routeArgument
->
getValue
()
!==
''
;
break
;
case
IntegerArgument
::
class
:
// @todo what about negative values?
$result
=
$routeArgument
->
getValue
()
>
0
;
break
;
default
:
throw
new
\
InvalidArgumentException
(
__CLASS__
.
' can\'t handle '
.
$class
);
}
if
(
!
$result
)
{
$routeArgument
->
addValidationError
(
new
ValidationError
(
sprintf
(
'The given value \'%s\' for argument \'%s\' must not be empty.'
,
$routeArgument
->
getValue
(),
$routeArgument
->
getName
()
)
)
);
}
}
}
extensions/ter/resources/schema/v1.json
View file @
fd47b158
...
...
@@ -265,11 +265,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -444,11 +446,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -634,11 +638,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -829,11 +835,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -1009,11 +1017,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -1208,11 +1218,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -1410,11 +1422,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -1625,11 +1639,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -1840,11 +1856,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -2056,11 +2074,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -2287,11 +2307,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
@@ -3464,11 +3486,13 @@
"type"
:
"array"
,
"example"
:
[
{
"name"
:
"argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for argument must be at max 50"
]
"some_arguemnt"
:
{
"name"
:
"some_argument"
,
"value"
:
100
,
"validationErrors"
:
[
"The given value 100 for some_argument must be at max 50"
]
}
}
],
"items"
:
{
...
...
extensions/ter/resources/schema/v1.yaml
View file @
fd47b158
...
...
@@ -192,10 +192,11 @@ paths:
invalidArguments
:
type
:
"
array"
example
:
-
name
:
"
argument"
value
:
100