Support inline examples as fuzzable values (#185)

* Support inline examples as fuzzable values

This change introduces the following behavior:

- If example values are specified in the Swagger specification, they
are plugged into default values in the grammar (instead of 'fuzzstring', etc.).
These do not affect the schema.

- If example payloads are specified outside Swagger, these are used
in the same way as before - both to determine the schema and to select values.

The two are mutually exclusive.  For example, if 'useQueryExamples' is specified,
and a query example is found in external payload examples, they are used and
the Swagger spec values are ignored.

* Also support multiple examples in the schema.

* another fix

* Remove the previous code to ignore values in the grammar, since

they may be example values.

Add logic to eliminate duplicates, which handles the default generated grammar and dictionary,
since they will be initialized to the same values.

* Refactoring to use the same default values in the grammar and dictionary.

* Fix unit tests.

* Fixes

* Fixes
This commit is contained in:
marina-p 2021-05-03 19:35:29 -07:00 коммит произвёл GitHub
Родитель 94e6cc4ba4
Коммит 071a532804
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 623 добавлений и 254 удалений

Просмотреть файл

@ -187,11 +187,13 @@
"properties": {
"id": {
"type": "integer",
"description": "The unique identifier of a blog post"
"description": "The unique identifier of a blog post",
"example": 123
},
"body": {
"type": "string",
"description": "Article content"
"description": "Article content",
"example": "first blog"
}
},
"type": "object"

Просмотреть файл

@ -543,6 +543,7 @@ class Request(object):
primitive_type = request_block[0]
default_val = request_block[1]
quoted = request_block[2]
examples = request_block[3]
values = []
# Handling dynamic primitives that need fresh rendering every time
@ -615,7 +616,7 @@ class Request(object):
values = [primitives.restler_refreshable_authentication_token]
# Handle all the rest
else:
values = candidate_values_pool.get_fuzzable_values(primitive_type, default_val, self._request_id, quoted)
values = candidate_values_pool.get_fuzzable_values(primitive_type, default_val, self._request_id, quoted, examples)
if Settings().fuzzing_mode == 'random-walk' and not preprocessing:
random.shuffle(values)

Просмотреть файл

@ -48,7 +48,9 @@ SHADOW_VALUES = "shadow_values"
QUOTED_ARG = 'quoted'
# Suffix present in always-unquoted primitive lists in the mutations dictionary.
UNQUOTED_STR = '_unquoted'
# Optional argument passed to fuzzable primitive definition function that can
# provide an example value. This value is used instead of the default value if present.
EXAMPLES_ARG = 'examples'
class CandidateValues(object):
def __init__(self):
self.unquoted_values = []
@ -265,7 +267,7 @@ class CandidateValuesPool(object):
except KeyError:
raise CandidateValueException
def get_fuzzable_values(self, primitive_type, default_value, request_id=None, quoted=False):
def get_fuzzable_values(self, primitive_type, default_value, request_id=None, quoted=False, examples=None):
""" Return list of fuzzable values with a default value (specified)
in the front of the list.
@ -291,8 +293,16 @@ class CandidateValuesPool(object):
if quoted:
default_value = f'"{default_value}"'
if examples:
# Use the examples instead of default value
# Quote the example values if needed
examples_quoted = [f'"{example_value}"' if quoted else example_value for example_value in examples]
fuzzable_values = examples_quoted + fuzzable_values
# Only use the default value if no values are defined in
# the dictionary for that fuzzable type
# the dictionary for that fuzzable type and there are no
# example values
if not fuzzable_values:
fuzzable_values.append(default_value)
elif primitive_type == FUZZABLE_DATETIME and\
@ -301,7 +311,14 @@ class CandidateValuesPool(object):
# two additional values for past/future in the list
fuzzable_values.insert(0, default_value)
return fuzzable_values
# Eliminate duplicates.
# Note: for the case when a default (non-example) value is in the grammar,
# the RESTler compiler initializes the dictionary and grammar with the same
# values. These duplicates will be eliminated here.
unique_fuzzable_values = []
[unique_fuzzable_values.append(x) for x in fuzzable_values if x not in unique_fuzzable_values]
return unique_fuzzable_values
def set_candidate_values(self, custom_values, per_endpoint_custom_mutations=None):
""" Overrides default primitive type values with user-provided ones.
@ -356,7 +373,8 @@ def restler_static_string(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_string(*args, **kwargs):
@ -379,7 +397,10 @@ def restler_fuzzable_string(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_int(*args, **kwargs):
""" Integer primitive.
@ -401,7 +422,10 @@ def restler_fuzzable_int(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_bool(*args, **kwargs):
@ -424,7 +448,10 @@ def restler_fuzzable_bool(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_number(*args, **kwargs):
@ -447,7 +474,10 @@ def restler_fuzzable_number(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_delim(*args, **kwargs):
@ -470,7 +500,10 @@ def restler_fuzzable_delim(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_group(*args, **kwargs):
@ -499,7 +532,10 @@ def restler_fuzzable_group(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, enum_vals, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, enum_vals, quoted, examples
def restler_fuzzable_uuid4(*args, **kwargs):
@ -522,7 +558,10 @@ def restler_fuzzable_uuid4(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_datetime(*args, **kwargs) :
@ -546,7 +585,10 @@ def restler_fuzzable_datetime(*args, **kwargs) :
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_fuzzable_object(*args, **kwargs) :
""" object primitive ({})
@ -568,7 +610,10 @@ def restler_fuzzable_object(*args, **kwargs) :
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples=None
if EXAMPLES_ARG in kwargs:
examples = kwargs[EXAMPLES_ARG]
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_multipart_formdata(*args, **kwargs):
""" Multipart/formdata primitive
@ -591,7 +636,8 @@ def restler_multipart_formdata(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_custom_payload(*args, **kwargs):
@ -614,7 +660,8 @@ def restler_custom_payload(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_custom_payload_header(*args, **kwargs):
@ -637,7 +684,8 @@ def restler_custom_payload_header(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_custom_payload_uuid4_suffix(*args, **kwargs):
@ -660,7 +708,8 @@ def restler_custom_payload_uuid4_suffix(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples
def restler_refreshable_authentication_token(*args, **kwargs):
@ -683,4 +732,5 @@ def restler_refreshable_authentication_token(*args, **kwargs):
quoted = False
if QUOTED_ARG in kwargs:
quoted = kwargs[QUOTED_ARG]
return sys._getframe().f_code.co_name, field_name, quoted
examples = None
return sys._getframe().f_code.co_name, field_name, quoted, examples

Просмотреть файл

@ -314,6 +314,7 @@ def custom_network_logging(sequence, candidate_values_pool, **kwargs):
primitive = request_block[0]
default_val = request_block[1]
quoted = request_block[2]
examples = request_block[3]
# Handling dynamic primitives that need fresh rendering every time
if primitive == "restler_fuzzable_uuid4":
values = [primitives.restler_fuzzable_uuid4]
@ -347,7 +348,7 @@ def custom_network_logging(sequence, candidate_values_pool, **kwargs):
default_val = values[0]
# Handle all the rest
else:
values = candidate_values_pool.get_fuzzable_values(primitive, default_val, request.request_id, quoted=quoted)
values = candidate_values_pool.get_fuzzable_values(primitive, default_val, request.request_id, quoted=quoted, examples=examples)
if len(values) > 1:
network_log.write(f"\t\t+ {primitive}: {values}")

Просмотреть файл

@ -416,4 +416,28 @@ module Examples =
Assert.True(grammar.Contains("_networkInterfaces__networkInterfaceName__put_properties_ipConfigurations_0_name.reader()"))
[<Fact>]
let ``inline examples are used instead of fuzzstring`` () =
let grammarOutputDirectoryPath = ctx.testRootDirPath
let config = { Restler.Config.SampleConfig with
IncludeOptionalParameters = true
// Make sure inline examples are used even if using examples is not specified
UseQueryExamples = None
UseBodyExamples = None
GrammarOutputDirectoryPath = Some grammarOutputDirectoryPath
SwaggerSpecFilePath = Some [(Path.Combine(Environment.CurrentDirectory, @"swagger\inline_examples.json"))]
CustomDictionaryFilePath = None
}
Restler.Workflow.generateRestlerGrammar None config
let grammarFilePath = Path.Combine(grammarOutputDirectoryPath, "grammar.py")
let grammar = File.ReadAllText(grammarFilePath)
Assert.True(grammar.Contains("primitives.restler_fuzzable_string(\"fuzzstring\", quoted=True, examples=[\"i5\"]),"))
Assert.True(grammar.Contains("primitives.restler_fuzzable_int(\"1\", examples=[\"32\"]),"))
Assert.True(grammar.Contains("primitives.restler_fuzzable_string(\"fuzzstring\", quoted=False, examples=[\"inline_example_value_laptop1\"]),"))
Assert.True(grammar.Contains("primitives.restler_fuzzable_string(\"fuzzstring\", quoted=False, examples=[\"inline_ex_2\"]),"))
Assert.True(grammar.Contains("primitives.restler_fuzzable_number(\"1.23\", examples=[\"1.67\"]),"))
interface IClassFixture<Fixtures.TestSetupAndCleanup>

Просмотреть файл

@ -156,6 +156,9 @@
<Content Include="swagger\headers.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="swagger\inline_examples.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="swagger\headers_dict.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -168,6 +171,9 @@
<Content Include="swagger\DependencyTests\examples\create_network_interface.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="swagger\examples\inline_payload_examples.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="swagger\DependencyTests\examples\create_application_gateway.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Просмотреть файл

@ -0,0 +1,7 @@
{
"parameters": {
"computerDimensions": ["external_example", "abcde"]
},
"responses": {
}
}

Просмотреть файл

@ -0,0 +1,97 @@
{
"basePath": "/api",
"consumes": [
"application/json"
],
"host": "localhost:8888",
"info": {
"description": "A simple swagger spec that uses inline examples",
"title": "Inline example spec",
"version": "1.0"
},
"definitions": {
"ComputerSpecs": {
"properties": {
"cpu": {
"description": "The cpu type",
"type": "string",
"example": "i5"
},
"memory": {
"description": "The amount of memory in GB",
"type": "integer",
"example": "32"
}
}
}
},
"paths": {
"/servers/{serverId}/restart": {
"post": {
"parameters": [
{
"in": "path",
"name": "serverId",
"required": true,
"type": "integer",
"example": "1234567"
},
{
"in": "header",
"name": "computerName",
"description": "The name of the server computer (targetMachine).",
"type": "string",
"fullTypeName": "System.String",
"required": true,
"example": "inline_example_value_laptop1"
},
{
"in": "query",
"name": "computerSize",
"required": true,
"type": "number",
"examples": {
"firstExample": "1.67",
"secondExample": "999.99"
}
},
{
"in": "query",
"name": "computerDimensions",
"required": true,
"type": "array",
"items": {
"type": "string"
},
"example": [ "inline_ex_1", "inline_ex_2" ],
"style": "form",
"explode": false
},
{
"in": "body",
"name": "computerSpecs",
"required": true,
"schema": {
"$ref": "#/definitions/ComputerSpecs"
},
"example": {
"cpu": "i7",
"memory": 12
}
}
],
"examples": {
},
"responses": {
"200": {
"description": "Success"
},
"404": {
"description": "Server not found."
}
}
}
}
},
"swagger": "2.0"
}

Просмотреть файл

@ -20,6 +20,7 @@ module Types =
{
defaultValue : string
isQuoted : bool
exampleValue : string option
}
/// RESTler grammar built-in types
@ -30,13 +31,13 @@ module Types =
| Restler_static_string_jtoken_delim of string
| Restler_fuzzable_string of RequestPrimitiveTypeData
| Restler_fuzzable_datetime of RequestPrimitiveTypeData
| Restler_fuzzable_object of string
| Restler_fuzzable_delim of string
| Restler_fuzzable_object of RequestPrimitiveTypeData
| Restler_fuzzable_delim of RequestPrimitiveTypeData
| Restler_fuzzable_uuid4 of RequestPrimitiveTypeData
| Restler_fuzzable_group of RequestPrimitiveTypeData
| Restler_fuzzable_bool of string
| Restler_fuzzable_int of string
| Restler_fuzzable_number of string
| Restler_fuzzable_bool of RequestPrimitiveTypeData
| Restler_fuzzable_int of RequestPrimitiveTypeData
| Restler_fuzzable_number of RequestPrimitiveTypeData
| Restler_multipart_formdata of string
| Restler_custom_payload of RequestPrimitiveTypeData
| Restler_custom_payload_header of string
@ -61,18 +62,18 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque
match p with
| Constant (t,v) ->
Restler_static_string_constant v
| Fuzzable (t,v) ->
| Fuzzable (t,v,exv) ->
match t with
| Bool -> Restler_fuzzable_bool v
| Bool -> Restler_fuzzable_bool { defaultValue = v ; isQuoted = false ; exampleValue = exv }
| PrimitiveType.DateTime ->
Restler_fuzzable_datetime { defaultValue = v ; isQuoted = isQuoted }
Restler_fuzzable_datetime { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv }
| PrimitiveType.String ->
Restler_fuzzable_string { defaultValue = v ; isQuoted = isQuoted }
| PrimitiveType.Object -> Restler_fuzzable_object v
| Int -> Restler_fuzzable_int v
| Number -> Restler_fuzzable_number v
Restler_fuzzable_string { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv }
| PrimitiveType.Object -> Restler_fuzzable_object { defaultValue = v ; isQuoted = false ; exampleValue = exv }
| Int -> Restler_fuzzable_int { defaultValue = v ; isQuoted = false; exampleValue = exv }
| Number -> Restler_fuzzable_number { defaultValue = v ; isQuoted = false ; exampleValue = exv }
| Uuid ->
Restler_fuzzable_uuid4 { defaultValue = v ; isQuoted = isQuoted }
Restler_fuzzable_uuid4 { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv }
| PrimitiveType.Enum (_, enumeration, defaultValue) ->
// TODO: should this be generating unique fuzzable group tags? Why is one needed?
let defaultStr =
@ -85,11 +86,11 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque
defaultStr
)
Restler_fuzzable_group
{ defaultValue = groupValue ; isQuoted = isQuoted }
{ defaultValue = groupValue ; isQuoted = isQuoted ; exampleValue = exv }
| Custom c ->
match c.payloadType with
| CustomPayloadType.String ->
Restler_custom_payload { defaultValue = c.payloadValue ; isQuoted = isQuoted }
Restler_custom_payload { defaultValue = c.payloadValue ; isQuoted = isQuoted ; exampleValue = None }
| CustomPayloadType.UuidSuffix ->
Restler_custom_payload_uuid4_suffix c.payloadValue
| CustomPayloadType.Header ->
@ -334,7 +335,7 @@ let generatePythonParameter includeOptionalParameters parameterKind (requestPara
| FuzzingPayload.Constant (primitiveType, v) ->
isPrimitiveTypeQuoted primitiveType (isNull v),
false
| FuzzingPayload.Fuzzable (primitiveType, _) ->
| FuzzingPayload.Fuzzable (primitiveType, _, _) ->
// Note: this is a current RESTler limitation -
// fuzzable values may not be set to null without changing the grammar.
isPrimitiveTypeQuoted primitiveType false,
@ -808,6 +809,14 @@ let getRequests(requests:Request list) includeOptionalParameters =
s, delim
let formatRestlerPrimitive p =
let getExamplePrimitiveParameter exv =
match exv with
| None -> ""
| Some str ->
let exStr, exDelim = quoteStringForPythonGrammar str
let quotedStr = sprintf "%s%s%s" exDelim exStr exDelim
sprintf ", examples=[%s]" quotedStr
let str =
match p with
| Restler_static_string_jtoken_delim s ->
@ -835,40 +844,56 @@ let getRequests(requests:Request list) includeOptionalParameters =
""
else
let str, delim = quoteStringForPythonGrammar s.defaultValue
sprintf "primitives.restler_fuzzable_string(%s%s%s, quoted=%s)"
delim str delim
let quotedDefaultString =
sprintf "%s%s%s" delim str delim
let exampleParameter = getExamplePrimitiveParameter s.exampleValue
sprintf "primitives.restler_fuzzable_string(%s, quoted=%s%s)"
quotedDefaultString
(if s.isQuoted then "True" else "False")
exampleParameter
| Restler_fuzzable_group s ->
sprintf "primitives.restler_fuzzable_group(%s,quoted=%s)"
sprintf "primitives.restler_fuzzable_group(%s,quoted=%s%s)"
s.defaultValue
(if s.isQuoted then "True" else "False")
(getExamplePrimitiveParameter s.exampleValue)
| Restler_fuzzable_int s ->
sprintf "primitives.restler_fuzzable_int(\"%s\")" s
sprintf "primitives.restler_fuzzable_int(\"%s\"%s)"
s.defaultValue
(getExamplePrimitiveParameter s.exampleValue)
| Restler_fuzzable_number s ->
sprintf "primitives.restler_fuzzable_number(\"%s\")" s
sprintf "primitives.restler_fuzzable_number(\"%s\"%s)"
s.defaultValue
(getExamplePrimitiveParameter s.exampleValue)
| Restler_fuzzable_bool s ->
sprintf "primitives.restler_fuzzable_bool(\"%s\")" s
sprintf "primitives.restler_fuzzable_bool(\"%s\"%s)"
s.defaultValue
(getExamplePrimitiveParameter s.exampleValue)
| Restler_fuzzable_datetime s ->
sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s)"
sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s%s)"
s.defaultValue
(if s.isQuoted then "True" else "False")
(getExamplePrimitiveParameter s.exampleValue)
| Restler_fuzzable_object s ->
if String.IsNullOrEmpty s then
if String.IsNullOrEmpty s.defaultValue then
printfn "ERROR: fuzzable objects should not be empty. Skipping."
""
else
let s, delim = quoteStringForPythonGrammar s
sprintf "primitives.restler_fuzzable_object(%s%s%s)" delim s delim
let str, delim = quoteStringForPythonGrammar s.defaultValue
let quotedDefaultString =
sprintf "%s%s%s" delim str delim
let exampleParameter = getExamplePrimitiveParameter s.exampleValue
sprintf "primitives.restler_fuzzable_object(%s%s)"
quotedDefaultString
exampleParameter
| Restler_fuzzable_uuid4 s ->
sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s)"
sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s%s)"
s.defaultValue
(if s.isQuoted then "True" else "False")
(getExamplePrimitiveParameter s.exampleValue)
| Restler_custom_payload p ->
sprintf "primitives.restler_custom_payload(\"%s\", quoted=%s)"
p.defaultValue
(if p.isQuoted then "True" else "False")
| Restler_custom_payload_uuid4_suffix p ->
sprintf "primitives.restler_custom_payload_uuid4_suffix(\"%s\")" p
| Restler_custom_payload_header p ->

Просмотреть файл

@ -19,6 +19,7 @@ open Restler.Examples
open Restler.Dictionary
open Restler.Compiler.SwaggerVisitors
open Restler.Utilities.Logging
open Restler.Utilities.Operators
exception UnsupportedParameterSerialization of string
@ -142,7 +143,7 @@ module private Parameters =
| OpenApiParameterStyle.Form ->
Some { style = StyleKind.Form ; explode = p.Explode }
| OpenApiParameterStyle.Simple ->
Some { style = StyleKind.Simple ; explode = p.Explode }
Some { style = StyleKind.Simple ; explode = p.Explode }
| OpenApiParameterStyle.Undefined ->
None
| _ ->
@ -154,7 +155,57 @@ module private Parameters =
ln.payload
| _ -> raise (UnsupportedType "Complex path parameters are not supported")
let pathParameters (swaggerMethodDefinition:OpenApiOperation) (endpoint:string) =
let private getParametersFromExample (examplePayload:ExampleRequestPayload)
(parameterList:seq<OpenApiParameter>) =
parameterList
|> Seq.choose (fun declaredParameter ->
// If the declared parameter isn't in the example, skip it. Here, the example is used to
// select which parameters must be passed to the API.
match examplePayload.parameterExamples
|> List.tryFind (fun r -> r.parameterName = declaredParameter.Name) with
| None -> None
| Some found ->
match found.payload with
| PayloadFormat.JToken payloadValue ->
let parameterGrammarElement =
generateGrammarElementForSchema declaredParameter.ActualSchema
(Some payloadValue, false) [] id
Some { name = declaredParameter.Name
payload = parameterGrammarElement
serialization = getParameterSerialization declaredParameter }
)
// Gets the first example found from the open API parameter:
// The priority is:
// - first, check the 'Example' property
// - then, check the 'Examples' property
let getExamplesFromParameter (p:OpenApiParameter) =
let schemaExample =
if isNull p.Schema then None
else
SchemaUtilities.tryGetSchemaExampleAsString p.Schema
match schemaExample with
| Some e ->
Some e
| None ->
if not (isNull p.Examples) then
if p.Examples.Count > 0 then
let firstExample = p.Examples.First()
let exValue =
let v = firstExample.Value.Value.ToString()
if p.Type = NJsonSchema.JsonObjectType.Array ||
p.Type = NJsonSchema.JsonObjectType.Object then
v
else
sprintf "\"%s\"" v
Some exValue
else
None
else
None
let pathParameters (swaggerMethodDefinition:OpenApiOperation) (endpoint:string)
(exampleConfig: ExampleRequestPayload list option) =
let declaredPathParameters = swaggerMethodDefinition.ActualParameters
|> Seq.filter (fun p -> p.Kind = NSwag.OpenApiParameterKind.Path)
@ -174,42 +225,42 @@ module private Parameters =
// By default, all path parameters are fuzzable (unless a producer or custom value is found for them later)
|> Seq.choose (fun part -> let parameterName = getPathParameterName part
let declaredParameter = allDeclaredPathParameters |> Seq.tryFind (fun p -> p.Name = parameterName)
match declaredParameter with
| None ->
printfn "Error: path parameter not found for parameter name: %s. This usually indicates an invalid Swagger file." parameterName
None
| Some parameter ->
let schema = parameter.ActualSchema
let leafProperty = getFuzzableValueForProperty ""
schema
true (*IsRequired*)
false (*IsReadOnly*)
(tryGetEnumeration schema)
(tryGetDefault schema)
let payload = LeafNode leafProperty
Some { name = parameterName ; payload = payload ; serialization = None })
let serialization = getParameterSerialization parameter
let schema = parameter.ActualSchema
// Check for path examples in the Swagger specification
// External path examples are not currently supported
match exampleConfig with
| None
| Some [] ->
let leafProperty =
if schema.IsArray then
raise (Exception("Arrays in path examples are not supported yet."))
else
let specExampleValue = getExamplesFromParameter parameter
getFuzzableValueForProperty ""
schema
true (*IsRequired*)
false (*IsReadOnly*)
(tryGetEnumeration schema)
(tryGetDefault schema)
specExampleValue
Some { name = parameterName
payload = LeafNode leafProperty
serialization = serialization }
| Some (firstExample::remainingExamples) ->
// Use the first example specified to determine the parameter value.
getParametersFromExample firstExample (parameter |> stn)
|> Seq.head
|> Some
)
ParameterList parameterList
let private getParametersFromExample (examplePayload:ExampleRequestPayload)
(parameterList:seq<OpenApiParameter>) =
parameterList
|> Seq.choose (fun declaredParameter ->
// If the declared parameter isn't in the example, skip it. Here, the example is used to
// select which parameters must be passed to the API.
match examplePayload.parameterExamples
|> List.tryFind (fun r -> r.parameterName = declaredParameter.Name) with
| None -> None
| Some found ->
match found.payload with
| PayloadFormat.JToken payloadValue ->
let parameterGrammarElement =
generateGrammarElementForSchema declaredParameter.ActualSchema (Some payloadValue) [] id
Some { name = declaredParameter.Name
payload = parameterGrammarElement
serialization = getParameterSerialization declaredParameter }
)
let private getParameters (parameterList:seq<OpenApiParameter>)
(exampleConfig:ExampleRequestPayload list option) (dataFuzzing:bool) =
@ -230,7 +281,15 @@ module private Parameters =
if dataFuzzing || examplePayloads.IsNone then
Some (parameterList
|> Seq.map (fun p ->
let parameterPayload = generateGrammarElementForSchema p.ActualSchema None [] id
let specExampleValue =
match getExamplesFromParameter p with
| None -> None
| Some exValue ->
SchemaUtilities.tryParseJToken exValue
let parameterPayload = generateGrammarElementForSchema
p.ActualSchema
(specExampleValue, true) [] id
{
name = p.Name
payload = parameterPayload
@ -510,9 +569,11 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list)
config.UseQueryExamples |> Option.defaultValue false
let useHeaderExamples =
config.UseHeaderExamples |> Option.defaultValue false
if useBodyExamples || useQueryExamples || useHeaderExamples || config.DiscoverExamples then
let usePathExamples =
config.UsePathExamples |> Option.defaultValue false
let useExamples =
usePathExamples || useBodyExamples || useQueryExamples || useHeaderExamples
if useExamples || config.DiscoverExamples then
let exampleRequestPayloads = getExampleConfig (ep,m.Key) m.Value config.DiscoverExamples config.ExamplesDirectory userSpecifiedExamples
// If 'discoverExamples' is specified, create a local copy in the specified examples directory for
// all the examples found.
@ -539,7 +600,12 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list)
if not config.ReadOnlyFuzz || readerMethods |> List.contains requestId.method then
let requestParameters =
{
RequestParameters.path = Parameters.pathParameters m.Value ep
RequestParameters.path =
let usePathExamples =
config.UsePathExamples |> Option.defaultValue false
Parameters.pathParameters
m.Value ep
(if usePathExamples then exampleConfig else None)
RequestParameters.header =
let useHeaderExamples =
config.UseHeaderExamples |> Option.defaultValue false
@ -570,7 +636,7 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list)
let allResponseProperties = seq {
for r in m.Value.Responses do
if validResponseCodes |> List.contains r.Key && not (isNull r.Value.ActualResponse.Schema) then
yield generateGrammarElementForSchema r.Value.ActualResponse.Schema None [] id
yield generateGrammarElementForSchema r.Value.ActualResponse.Schema (None, false) [] id
}
// 'allResponseProperties' contains the schemas of all possible responses

Просмотреть файл

@ -60,6 +60,8 @@ type Config =
UseHeaderExamples : bool option
UsePathExamples : bool option
UseQueryExamples : bool option
UseBodyExamples : bool option
@ -193,6 +195,7 @@ let SampleConfig =
ExampleConfigFilePath = None
GrammarOutputDirectoryPath = None
IncludeOptionalParameters = true
UsePathExamples = None
UseQueryExamples = None
UseBodyExamples = None
UseHeaderExamples = None
@ -224,6 +227,7 @@ let DefaultConfig =
UseQueryExamples = Some true
UseHeaderExamples = None
UseBodyExamples = Some true
UsePathExamples = None
DiscoverExamples = false
ExamplesDirectory = ""
ResolveQueryDependencies = true

Просмотреть файл

@ -734,7 +734,7 @@ let getParameterDependencies parameterKind globalAnnotations
let resourceAccessPath = PropertyAccessPaths.getLeafAccessPath parentAccessPath p
let primitiveType =
match p.payload with
| FuzzingPayload.Fuzzable (pt, _) -> Some pt
| FuzzingPayload.Fuzzable (pt, _, _) -> Some pt
| FuzzingPayload.Constant (pt, _) -> Some pt
| FuzzingPayload.Custom c -> Some c.primitiveType
| _ -> None
@ -1218,7 +1218,7 @@ module DependencyLookup =
else
let defaultPayload =
match p.payload with
| None -> Fuzzable (PrimitiveType.String, "")
| None -> Fuzzable (PrimitiveType.String, "", None)
| Some p -> p
let propertyAccessPath =
{ path = PropertyAccessPaths.getInnerAccessPath resourceAccessPath p
@ -1235,7 +1235,7 @@ module DependencyLookup =
// First, check if the parameter itself has a dependency
let (parameterName, properties) = requestParameter.name, requestParameter.payload
let defaultPayload = (Fuzzable (PrimitiveType.String, ""))
let defaultPayload = (Fuzzable (PrimitiveType.String, "", None))
let dependencyPayload = getConsumerPayload dependencies pathPayload requestId parameterName EmptyAccessPath defaultPayload
let payloadWithDependencies =

Просмотреть файл

@ -121,15 +121,15 @@ type MutationsDictionary =
/// The default mutations dictionary generated when a user does not specify it
let DefaultMutationsDictionary =
{
restler_fuzzable_string = ["fuzzstring"]
restler_fuzzable_string = [DefaultPrimitiveValues.[PrimitiveType.String]]
restler_fuzzable_string_unquoted = []
restler_fuzzable_int = ["0" ; "1"]
restler_fuzzable_number = ["0.1"; "1.2"]
restler_fuzzable_bool = ["true"]
restler_fuzzable_datetime = ["6/25/2019 12:00:00 AM"]
restler_fuzzable_int = [DefaultPrimitiveValues.[PrimitiveType.Int]; "0"]
restler_fuzzable_number = [DefaultPrimitiveValues.[PrimitiveType.Number]]
restler_fuzzable_bool = [DefaultPrimitiveValues.[PrimitiveType.Bool]]
restler_fuzzable_datetime = [DefaultPrimitiveValues.[PrimitiveType.DateTime]]
restler_fuzzable_datetime_unquoted = []
restler_fuzzable_object = ["{}"]
restler_fuzzable_uuid4 = ["903bcc44-30cf-4ea7-968a-d9d0da7c072f"]
restler_fuzzable_object = [DefaultPrimitiveValues.[PrimitiveType.Object]]
restler_fuzzable_uuid4 = [DefaultPrimitiveValues.[PrimitiveType.Uuid]]
restler_fuzzable_uuid4_unquoted = []
restler_custom_payload = Some (Map.empty<string, string list>)
restler_custom_payload_unquoted = Some (Map.empty<string, string list>)

Просмотреть файл

@ -134,8 +134,9 @@ type FuzzingPayload =
/// Example: (Int "1")
| Constant of PrimitiveType * string
/// Example: (Int "1")
| Fuzzable of PrimitiveType * string
/// (data type, default value, example value)
/// Example: (Int "1", "2")
| Fuzzable of PrimitiveType * string * string option
/// The custom payload, as specified in the fuzzing dictionary
| Custom of CustomPayload
@ -400,3 +401,19 @@ let generatePrefixForCustomUuidSuffixPayload (suffixPayloadId:string) =
sprintf "%s" suffixPayloadId
else
sprintf "%s" (suffixPayloadIdRestricted |> Seq.map string |> String.concat "")
/// This map lists the default primitive values for fuzzable primitives
/// These will be used both in the grammar and dictionary file.
let DefaultPrimitiveValues =
[
PrimitiveType.String, "fuzzstring" // Note: quotes are intentionally omitted.
PrimitiveType.Uuid, "566048da-ed19-4cd3-8e0a-b7e0e1ec4d72" // Note: quotes are intentionally omitted.
PrimitiveType.DateTime, "2019-06-26T20:20:39+00:00" // Note: quotes are intentionally omitted.
PrimitiveType.Number, "1.23" // Note: quotes are intentionally omitted.
PrimitiveType.Int, "1"
PrimitiveType.Bool, "true"
PrimitiveType.Object, "{ \"fuzz\": false }"
]
|> Map.ofSeq

Просмотреть файл

@ -24,7 +24,8 @@ let getYamlSwaggerDocumentAsync (path:string) = async {
let getSwaggerDocument (swaggerPath:string) (workingDirectory:string) =
async {
let specExtension = System.IO.Path.GetExtension(swaggerPath)
// When a spec is preprocessed, it is converted to json
let specExtension = ".json"
let specName = sprintf "%s%s%s" (System.IO.Path.GetFileNameWithoutExtension(swaggerPath))
"_preprocessed"

Просмотреть файл

@ -7,6 +7,8 @@ open System
open Restler.Grammar
open Tree
open Restler.Utilities.Operators
open Newtonsoft.Json.Linq
open NJsonSchema
exception UnsupportedType of string
exception NullArraySchema of string
@ -14,38 +16,100 @@ exception UnsupportedArrayExample of string
exception UnsupportedRecursiveExample of string
module SchemaUtilities =
let getGrammarPrimitiveTypeWithDefaultValue (objectType:NJsonSchema.JsonObjectType) (format:string) =
match objectType with
| NJsonSchema.JsonObjectType.String ->
let defaultStringType = PrimitiveType.String, "fuzzstring" // Note: quotes are intentionally omitted.
if not (isNull format) then
match (format.ToLower()) with
| "uuid"
| "guid" ->
PrimitiveType.Uuid, "566048da-ed19-4cd3-8e0a-b7e0e1ec4d72" // Note: quotes are intentionally omitted.
| "date-time" ->
PrimitiveType.DateTime, "2019-06-26T20:20:39+00:00" // Note: quotes are intentionally omitted.
| "double" ->
PrimitiveType.Number, "1.23" // Note: quotes are intentionally omitted.
| _ ->
printfn "found unsupported format: %s" format
defaultStringType
else
defaultStringType
| NJsonSchema.JsonObjectType.Number ->
PrimitiveType.Number, "1.23"
| NJsonSchema.JsonObjectType.Integer ->
PrimitiveType.Int, "1"
| NJsonSchema.JsonObjectType.Boolean ->
PrimitiveType.Bool, "true"
| NJsonSchema.JsonObjectType.Object ->
PrimitiveType.Object, "{ \"fuzz\": false }"
| NJsonSchema.JsonObjectType.Array
| _ ->
raise (UnsupportedType (sprintf "%A is not a fuzzable primitive type. Please make sure your Swagger file is valid." objectType))
let getFuzzableValueForObjectType (objectType:NJsonSchema.JsonObjectType) (format:string) =
Fuzzable (getGrammarPrimitiveTypeWithDefaultValue objectType format)
/// Get an example value as a string, either directly from the 'example' attribute or
/// from the extension 'Examples' property.
let tryGetSchemaExampleValue (schema:NJsonSchema.JsonSchema) =
if not (isNull schema.Example) then
Some (schema.Example.ToString())
else if not (isNull schema.ExtensionData) then
let extensionDataExample =
schema.ExtensionData
|> Seq.tryFind (fun kvp -> kvp.Key.ToLower() = "examples")
match extensionDataExample with
| None -> None
| Some example ->
let dict = example.Value :?> System.Collections.IDictionary
let specExampleValues = seq {
if not (isNull dict) then
for exampleValue in dict.Values do
let valueAsJson =
match exampleValue with
| :? string -> (exampleValue.ToString())
| _ ->
Microsoft.FSharpLu.Json.Compact.serialize exampleValue
yield valueAsJson
}
specExampleValues |> Seq.tryHead
else None
/// Get the example from the schema.
/// 'None' will be returned if the example for an
/// object or array cannot be successfully parsed.
let tryGetSchemaExampleAsString (schema:NJsonSchema.JsonSchema) =
tryGetSchemaExampleValue schema
let tryParseJToken (exampleValue:String) =
try
JToken.Parse(exampleValue)
|> Some
with ex ->
None
let tryGetSchemaExampleAsJToken (schema:NJsonSchema.JsonSchema) =
match tryGetSchemaExampleValue schema with
| Some valueAsString ->
tryParseJToken valueAsString
| None -> None
let getGrammarPrimitiveTypeWithDefaultValue (objectType:NJsonSchema.JsonObjectType)
(format:string)
(exampleValue:string option) :
(PrimitiveType * string * string option) =
let defaultTypeWithValue =
match objectType with
| NJsonSchema.JsonObjectType.String ->
let defaultStringType =
PrimitiveType.String, DefaultPrimitiveValues.[PrimitiveType.String]
if not (isNull format) then
match (format.ToLower()) with
| "uuid"
| "guid" ->
PrimitiveType.Uuid,
DefaultPrimitiveValues.[PrimitiveType.Uuid]
| "date-time" ->
PrimitiveType.DateTime,
DefaultPrimitiveValues.[PrimitiveType.DateTime]
| "double" ->
PrimitiveType.Number,
DefaultPrimitiveValues.[PrimitiveType.Number]
| _ ->
printfn "found unsupported format: %s" format
defaultStringType
else
defaultStringType
| NJsonSchema.JsonObjectType.Number ->
PrimitiveType.Number,
DefaultPrimitiveValues.[PrimitiveType.Number]
| NJsonSchema.JsonObjectType.Integer ->
PrimitiveType.Int,
DefaultPrimitiveValues.[PrimitiveType.Int]
| NJsonSchema.JsonObjectType.Boolean ->
PrimitiveType.Bool,
DefaultPrimitiveValues.[PrimitiveType.Bool]
| NJsonSchema.JsonObjectType.Object ->
PrimitiveType.Object,
DefaultPrimitiveValues.[PrimitiveType.Object]
| NJsonSchema.JsonObjectType.Array
| _ ->
raise (UnsupportedType (sprintf "%A is not a fuzzable primitive type. Please make sure your Swagger file is valid." objectType))
let (primitiveType, defaultValue) = defaultTypeWithValue
primitiveType, defaultValue, exampleValue
let getFuzzableValueForObjectType (objectType:NJsonSchema.JsonObjectType) (format:string) (exampleValue: string option) =
Fuzzable (getGrammarPrimitiveTypeWithDefaultValue objectType format exampleValue)
/// Get a boolean property from 'ExtensionData', if it exists.
let getExtensionDataBooleanPropertyValue (extensionData:System.Collections.Generic.IDictionary<string, obj>) (extensionDataKeyName:string) =
@ -64,11 +128,8 @@ module SchemaUtilities =
open SchemaUtilities
module SwaggerVisitors =
open Newtonsoft.Json.Linq
open NJsonSchema
let getFuzzableValueForProperty propertyName (propertySchema:NJsonSchema.JsonSchema) isRequired isReadOnly enumeration defaultValue =
let getFuzzableValueForProperty propertyName (propertySchema:NJsonSchema.JsonSchema) isRequired isReadOnly enumeration defaultValue (exampleValue:string option) =
let payload =
match propertySchema.Type with
| NJsonSchema.JsonObjectType.String
@ -76,23 +137,23 @@ module SwaggerVisitors =
| NJsonSchema.JsonObjectType.Integer
| NJsonSchema.JsonObjectType.Boolean ->
match enumeration with
| None -> getFuzzableValueForObjectType propertySchema.Type propertySchema.Format
| None -> getFuzzableValueForObjectType propertySchema.Type propertySchema.Format exampleValue
| Some ev ->
let enumValues = ev |> Seq.map (fun e -> string e) |> Seq.toList
let grammarPrimitiveType, _ = getGrammarPrimitiveTypeWithDefaultValue propertySchema.Type propertySchema.Format
let grammarPrimitiveType,_,exv = getGrammarPrimitiveTypeWithDefaultValue propertySchema.Type propertySchema.Format exampleValue
let defaultFuzzableEnumValue =
match enumValues with
| [] -> "null"
| h::rest -> h
Fuzzable (PrimitiveType.Enum (grammarPrimitiveType, enumValues, defaultValue), defaultFuzzableEnumValue)
Fuzzable (PrimitiveType.Enum (grammarPrimitiveType, enumValues, defaultValue), defaultFuzzableEnumValue, exv)
| NJsonSchema.JsonObjectType.Object
| NJsonSchema.JsonObjectType.None ->
// Example of JsonObjectType.None: "content": {} without a type specified in Swagger.
// Let's treat these the same as Object.
getFuzzableValueForObjectType NJsonSchema.JsonObjectType.Object propertySchema.Format
getFuzzableValueForObjectType NJsonSchema.JsonObjectType.Object propertySchema.Format exampleValue
| NJsonSchema.JsonObjectType.File ->
// Fuzz it as a string.
Fuzzable (PrimitiveType.String, "file object")
Fuzzable (PrimitiveType.String, "file object", None)
| nst ->
raise (UnsupportedType (sprintf "Unsupported type formatting: %A" nst))
{ LeafProperty.name = propertyName; payload = payload ;isRequired = isRequired ; isReadOnly = isReadOnly }
@ -107,12 +168,11 @@ module SwaggerVisitors =
then None
else Some (property.Default.ToString())
module GenerateGrammarElements =
/// Returns the specified property when the object contains it.
/// Note: if the example object does not contain the property,
let extractPropertyFromObject propertyName (objectType:NJsonSchema.JsonObjectType) (exampleObj: JToken option) =
let extractPropertyFromObject propertyName (objectType:NJsonSchema.JsonObjectType)
(exampleObj: JToken option) =
if (String.IsNullOrWhiteSpace propertyName && objectType <> NJsonSchema.JsonObjectType.Array) then
failwith "non-array should always have a property name"
let pv, includeProperty =
@ -170,27 +230,35 @@ module SwaggerVisitors =
else rawValue
| _ -> rawValue
module ExampleHelpers =
/// Given a JSON array, returns the example value that should be used.
let getArrayExamples (pv:JToken option) =
let arrayExamples =
/// Given a JSON array, returns the example value that should be used.
let getArrayExamples (pv:JToken option) =
match pv with
| Some exv -> exv.Children() |> seq
| None -> Seq.empty
| Some exv ->
let maxArrayElementsFromExample = 5
exv.Children() |> seq
|> Seq.truncate maxArrayElementsFromExample
|> Some
| None -> None
if arrayExamples |> Seq.isEmpty then
None |> stn
else
let maxArrayElementsFromExample = 5
arrayExamples
|> Seq.truncate maxArrayElementsFromExample
|> Seq.map (fun example -> Some example)
let tryGetArraySchemaExample (schema:NJsonSchema.JsonSchema) =
// Check for local examples from the Swagger spec if external examples were not specified.
let schemaExampleValue = tryGetSchemaExampleAsJToken schema
let schemaArrayExamples = getArrayExamples schemaExampleValue
if schemaArrayExamples.IsNone || schemaArrayExamples.Value |> Seq.isEmpty then
None
else
schemaArrayExamples.Value
|> Seq.map (fun example -> (Some example, true))
|> Some
let rec processProperty (propertyName, property:NJsonSchema.JsonSchemaProperty)
(propertyValue: JToken option)
(parents:NJsonSchema.JsonSchema list)
(cont: Tree<LeafProperty, InnerProperty> -> Tree<LeafProperty, InnerProperty>) =
(propertyPayloadExampleValue: JToken option, generateFuzzablePayload:bool)
(parents:NJsonSchema.JsonSchema list)
(cont: Tree<LeafProperty, InnerProperty> -> Tree<LeafProperty, InnerProperty>) =
// 'isReadOnly' is not correctly initialized in NJsonSchema. Instead, it appears
// in ExtensionData
@ -199,43 +267,51 @@ module SwaggerVisitors =
| None -> property.IsReadOnly
| Some v -> v
// If an example value was not specified, also check for a locally defined example
// in the Swagger specification.
match property.Type with
| NJsonSchema.JsonObjectType.String
| NJsonSchema.JsonObjectType.Number
| NJsonSchema.JsonObjectType.Integer
| NJsonSchema.JsonObjectType.Boolean ->
let propertyPayload =
let fuzzablePropertyPayload =
let propertySchema = (property :> NJsonSchema.JsonSchema)
let schemaExampleValue = SchemaUtilities.tryGetSchemaExampleAsString property
getFuzzableValueForProperty propertyName
propertySchema
property.IsRequired
(propertyIsReadOnly property)
(tryGetEnumeration propertySchema)
(tryGetDefault propertySchema)
schemaExampleValue
let propertyPayload =
match propertyPayloadExampleValue with
| None ->
// If a payload example is not specified, generate a fuzzable payload.
fuzzablePropertyPayload
| Some v ->
let examplePropertyPayload =
match fuzzablePropertyPayload.payload with
| Fuzzable (primitiveType, defaultValue, _) ->
let payloadValue = GenerateGrammarElements.formatJTokenProperty primitiveType v
// Replace the default payload with the example payload, preserving type information.
// 'generateFuzzablePayload' is specified a schema example is found for the parent
// object (e.g. an array).
if generateFuzzablePayload then
Fuzzable (primitiveType, defaultValue, Some payloadValue)
else
Constant (primitiveType, payloadValue)
| _ -> raise (invalidOp(sprintf "invalid payload %A, expected fuzzable" fuzzablePropertyPayload))
match propertyValue with
| None ->
LeafNode propertyPayload
|> cont
| Some v ->
let examplePayload =
match propertyPayload.payload with
| Fuzzable (primitiveType, _) ->
// Replace the default payload with the example payload, preserving type information.
Constant (primitiveType, GenerateGrammarElements.formatJTokenProperty primitiveType v)
| _ -> raise (invalidOp(sprintf "invalid payload %A, expected fuzzable" propertyValue))
LeafNode { propertyPayload with payload = examplePayload }
|> cont
{ fuzzablePropertyPayload with payload = examplePropertyPayload }
LeafNode propertyPayload
| NJsonSchema.JsonObjectType.None ->
// When the object type is 'None', simply pass through the example value ('propertyValue')
// For example: the schema of a property whose schema is declared as a $ref will be visited here.
// The example property value needs to be examined according to the 'ActualSchema', which
// is passed through below.
generateGrammarElementForSchema property.ActualSchema propertyValue parents (fun tree ->
generateGrammarElementForSchema property.ActualSchema (propertyPayloadExampleValue, generateFuzzablePayload) parents (fun tree ->
let innerProperty = { InnerProperty.name = propertyName
payload = None
propertyType = Property
@ -245,50 +321,25 @@ module SwaggerVisitors =
|> cont
)
| NJsonSchema.JsonObjectType.Array ->
let pv, includeProperty = GenerateGrammarElements.extractPropertyFromObject propertyName property.Type propertyValue
if not includeProperty then
// TODO: need test case for this example. Raise an exception to flag these cases.
raise (UnsupportedArrayExample (sprintf "Property name: %s" propertyName))
else
// Exit gracefully in case the Swagger is not valid and does not declare the array schema
if isNull property.Item then
raise (NullArraySchema "Make sure the array definition has an element type in Swagger.")
let arrayExamples = getArrayExamples pv
if arrayExamples |> Seq.length = 1 then
generateGrammarElementForSchema property.Item.ActualSchema (arrayExamples |> Seq.head) parents (fun tree ->
let innerProperty =
{ InnerProperty.name = propertyName
payload = None; propertyType = Array
isRequired = true
isReadOnly = (propertyIsReadOnly property) }
if pv.IsSome then
InternalNode (innerProperty, Seq.empty)
else
InternalNode (innerProperty, stn tree)
|> cont
)
else
let arrayElements =
arrayExamples
|> Seq.map (fun example ->
generateGrammarElementForSchema property.Item.ActualSchema
example
parents
(fun tree -> tree)
|> cont
)
let innerArrayProperty =
{ InnerProperty.name = propertyName; payload = None; propertyType = Array
isRequired = true
isReadOnly = (propertyIsReadOnly property) }
InternalNode (innerArrayProperty, arrayElements)
let innerArrayProperty =
{ InnerProperty.name = propertyName
payload = None; propertyType = Array
isRequired = true
isReadOnly = (propertyIsReadOnly property) }
let arrayWithElements =
generateGrammarElementForSchema property (propertyPayloadExampleValue, generateFuzzablePayload)
parents (fun tree -> tree)
|> cont
match arrayWithElements with
| InternalNode (n, tree) ->
InternalNode (innerArrayProperty, tree)
| LeafNode _ ->
raise (invalidOp("An array should be an internal node."))
| NJsonSchema.JsonObjectType.Object ->
// This object may or may not have nested properties.
// Similar to type 'None', just pass through the object and it will be taken care of downstream.
generateGrammarElementForSchema property.ActualSchema None parents (fun tree ->
generateGrammarElementForSchema property.ActualSchema (None, false) parents (fun tree ->
// If the object has no properties, it should be set to its primitive type.
match tree with
| LeafNode l ->
@ -307,7 +358,7 @@ module SwaggerVisitors =
raise (UnsupportedType (sprintf "Found unsupported type in body parameters: %A" nst))
and generateGrammarElementForSchema (schema:NJsonSchema.JsonSchema)
(exampleValue:JToken option)
(exampleValue:JToken option, generateFuzzablePayloadsForExamples:bool)
(parents:NJsonSchema.JsonSchema list)
(cont: Tree<LeafProperty, InnerProperty> -> Tree<LeafProperty, InnerProperty>) =
@ -328,7 +379,7 @@ module SwaggerVisitors =
// TODO: need test case for this example. Raise an exception to flag these cases.
// Most likely, the current example value should simply be used in the leaf property
raise (UnsupportedRecursiveExample (sprintf "%A" exampleValue))
let leafProperty = { LeafProperty.name = ""; payload = Fuzzable (PrimitiveType.String, ""); isRequired = true ; isReadOnly = false }
let leafProperty = { LeafProperty.name = ""; payload = Fuzzable (PrimitiveType.String, "", None); isRequired = true ; isReadOnly = false }
LeafNode leafProperty
|> cont
else
@ -341,31 +392,40 @@ module SwaggerVisitors =
let exValue, includeProperty = GenerateGrammarElements.extractPropertyFromObject name NJsonSchema.JsonObjectType.String exampleValue
if not includeProperty then None
else
Some (processProperty (name, item.Value) exValue (schema::parents) id))
Some (processProperty (name, item.Value)
(exValue, generateFuzzablePayloadsForExamples)
(schema::parents) id))
let arrayProperties =
if schema.IsArray then
// An example of this is an array type query parameter.
let exValue, includeProperty = GenerateGrammarElements.extractPropertyFromArray exampleValue
if not includeProperty then Seq.empty
let arrayPayloadExampleValue, includeProperty = GenerateGrammarElements.extractPropertyFromArray exampleValue
if not includeProperty then
Seq.empty
else
let arrayExamples = getArrayExamples exValue
if arrayExamples |> Seq.length = 1 then
// Corner case: if this is an array element with a specified empty array as an example,
// set the array element payload to the override value (an empty string).
if exValue.IsSome then
Seq.empty
else
generateGrammarElementForSchema schema.Item.ActualSchema (arrayExamples |> Seq.head) (schema::parents) id
let payloadArrayExamples = ExampleHelpers.getArrayExamples arrayPayloadExampleValue
match payloadArrayExamples with
| None ->
let schemaArrayExamples = ExampleHelpers.tryGetArraySchemaExample schema
match schemaArrayExamples with
| None ->
generateGrammarElementForSchema schema.Item.ActualSchema (None, false) (schema::parents) id
|> stn
else
| Some sae ->
sae |> Seq.map (fun (schemaExampleValue, generateFuzzablePayload) ->
generateGrammarElementForSchema schema.Item.ActualSchema
(schemaExampleValue, generateFuzzablePayload)
(schema::parents)
id
)
| Some payloadArrayExamples when payloadArrayExamples |> Seq.isEmpty ->
Seq.empty
| Some payloadArrayExamples ->
let arrayElements =
arrayExamples
payloadArrayExamples
|> Seq.map (fun example ->
generateGrammarElementForSchema
schema.Item.ActualSchema
example
(Some example, false)
(schema::parents)
id |> stn)
|> Seq.concat
@ -374,7 +434,7 @@ module SwaggerVisitors =
let allOfParameterSchemas =
schema.AllOf
|> Seq.map (fun ao -> ao, generateGrammarElementForSchema ao.ActualSchema exampleValue (schema::parents) id)
|> Seq.map (fun ao -> ao, generateGrammarElementForSchema ao.ActualSchema (exampleValue, false) (schema::parents) id)
let allOfProperties =
allOfParameterSchemas
@ -412,24 +472,27 @@ module SwaggerVisitors =
} |> Seq.concat
if internalNodes |> Seq.isEmpty then
// If there is an example, use it (constant) instead of the token
match exampleValue with
| None ->
if schema.Type = JsonObjectType.None && allOfSchema.IsSome then
LeafNode allOfSchema.Value
|> cont
else
LeafNode (getFuzzableValueForProperty
""
schema
true (*IsRequired*)
false (*IsReadOnly*)
(tryGetEnumeration schema) (*enumeration*)
(tryGetDefault schema) (*defaultValue*))
|> cont
let leafProperty =
if schema.Type = JsonObjectType.None && allOfSchema.IsSome then
allOfSchema.Value
else
// Check for local examples from the Swagger spec if external examples were not specified.
// A fuzzable payload with a local example as the default value will be generated.
let specExampleValue = tryGetSchemaExampleAsString schema
getFuzzableValueForProperty
""
schema
true (*IsRequired*)
false (*IsReadOnly*)
(tryGetEnumeration schema) (*enumeration*)
(tryGetDefault schema) (*defaultValue*)
specExampleValue
LeafNode leafProperty
|> cont
| Some v ->
// Either none of the above properties matched, or there are no properties and
// this is a leaf object.
let isLeafObject = (schema.Properties |> Seq.isEmpty &&
@ -441,10 +504,15 @@ module SwaggerVisitors =
if schema.Type = JsonObjectType.None then JsonObjectType.Object
else schema.Type
let primitiveType, _ = getGrammarPrimitiveTypeWithDefaultValue schemaType schema.Format
let primitiveType, defaultValue, _ = getGrammarPrimitiveTypeWithDefaultValue schemaType schema.Format None
let leafPayload =
FuzzingPayload.Constant (primitiveType, GenerateGrammarElements.formatJTokenProperty primitiveType v)
let exampleValue = GenerateGrammarElements.formatJTokenProperty primitiveType v
if generateFuzzablePayloadsForExamples then
FuzzingPayload.Fuzzable (primitiveType, defaultValue, Some exampleValue)
else
FuzzingPayload.Constant (primitiveType, exampleValue)
LeafNode ({ LeafProperty.name = ""
payload = leafPayload
isRequired = true