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:
Родитель
94e6cc4ba4
Коммит
071a532804
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче