diff --git a/docs/user-guide/CompilerConfig.md b/docs/user-guide/CompilerConfig.md index 1f688eb..9a3e4a6 100644 --- a/docs/user-guide/CompilerConfig.md +++ b/docs/user-guide/CompilerConfig.md @@ -96,4 +96,7 @@ catch consistency bugs in the specification because producer-consumer dependenci PascalCase HyphenSeparator UnderscoreSeparator - ``` \ No newline at end of file + ``` +* *TrackFuzzedParameterNames* False by default. When true, every fuzzable primitive will +include an additional parameter `param_name` which is the name of the property or +parameter being fuzzed. These will be used to capture fuzzed parameters in ```tracked_parameters``` in the spec coverage file. \ No newline at end of file diff --git a/docs/user-guide/Testing.md b/docs/user-guide/Testing.md index 947d7e6..0fa2dea 100644 --- a/docs/user-guide/Testing.md +++ b/docs/user-guide/Testing.md @@ -105,7 +105,8 @@ During each Test run a `speccov.json` file will be created in the logs directory "response_body": "{\n \"errors\": {\n \"id\": \"'5882' is not of type 'integer'\"\n },\n \"message\": \"Input payload validation failed\"\n}\n" }, "tracked_parameters": { - "per_page_9": "2" + "per_page": ["2"], + "page": ["1"] } }, ``` @@ -135,7 +136,9 @@ the coverage data is being reported. * The __"tracked_parameters"__ property is optional and generated only when using `Test` mode with ```test-all-combinations```. This property contains key-value pairs of all of the parameters -for which more than one value is being tested. +for which more than one value is being tested. By default, enums and custom payloads +are always tracked. In addition, when the specification was compiled with +`TrackFuzzedParameterNames` set to `true`, all fuzzable parameters will be tracked. #### Postprocessing Scripts: The `utilities` directory contains a sub-directory called `speccovparsing` that contains scripts for postprocessing speccov files. diff --git a/restler/engine/core/requests.py b/restler/engine/core/requests.py index bc1b34b..34f0cd0 100644 --- a/restler/engine/core/requests.py +++ b/restler/engine/core/requests.py @@ -602,11 +602,10 @@ class Request(object): quoted = request_block[2] examples = request_block[3] else: - field_name = None default_val = request_block[1] quoted = request_block[2] examples = request_block[3] - + field_name = request_block[4] values = [] # Handling dynamic primitives that need fresh rendering every time if primitive_type == primitives.FUZZABLE_UUID4: @@ -696,9 +695,10 @@ class Request(object): # Only track the parameter if there are multiple values being combined if len(values) > 1: if not field_name: - field_name = "tracked_param" - field_name = f"{field_name}_{param_idx}" - tracked_parameters[field_name] = param_idx + field_name = f"tracked_param_{param_idx}" + if field_name not in tracked_parameters: + tracked_parameters[field_name] = [] + tracked_parameters[field_name].append(param_idx) fuzzable.append(values) @@ -718,9 +718,10 @@ class Request(object): values = request_utilities.resolve_dynamic_primitives(values, candidate_values_pool) tracked_parameter_values = {} - for (k, idx) in tracked_parameters.items(): - tracked_parameter_values[k] = values[idx] - + for (k, idx_list) in tracked_parameters.items(): + tracked_parameter_values[k] = [] + for idx in idx_list: + tracked_parameter_values[k].append(values[idx]) rendered_data = "".join(values) yield rendered_data, parser, tracked_parameter_values @@ -764,6 +765,25 @@ class Request(object): return self._total_feasible_combinations + def update_tracked_parameters(self, tracked_parameters): + """ Updates tracked parameters for the request by merging with the + existing parameters. + + The tracked parameters are currently a set of names, with arrays of + values that were observed during sequence rendering. + + @param tracked_parameters: The tracked parameters and their values. + @type tracked_parameters: Dict + + @return: None + @rtype : None + """ + for param_name, param_val_list in tracked_parameters.items(): + for param_val in param_val_list: + if param_name not in self._tracked_parameters: + self._tracked_parameters[param_name] = [] + self._tracked_parameters[param_name].append(param_val) + def GrammarRequestCollection(): """ Accessor for the global request collection singleton """ return GlobalRequestCollection.Instance()._req_collection diff --git a/restler/engine/core/sequences.py b/restler/engine/core/sequences.py index c2702d0..b022442 100644 --- a/restler/engine/core/sequences.py +++ b/restler/engine/core/sequences.py @@ -350,7 +350,8 @@ class Sequence(object): dependencies.reset_tlb() sequence_failed = False - request._tracked_parameters = tracked_parameters + request._tracked_parameters = {} + request.update_tracked_parameters(tracked_parameters) # Step A: Static template rendering # Render last known valid combination of primitive type values # for every request until the last @@ -360,7 +361,7 @@ class Sequence(object): prev_request.render_current(candidate_values_pool, preprocessing=preprocessing) - request._tracked_parameters.update(tracked_parameters) + request.update_tracked_parameters(tracked_parameters) # substitute reference placeholders with resolved values if not Settings().ignore_dependencies: diff --git a/restler/engine/fuzzing_parameters/body_schema.py b/restler/engine/fuzzing_parameters/body_schema.py index 624d59f..62fc0f1 100644 --- a/restler/engine/fuzzing_parameters/body_schema.py +++ b/restler/engine/fuzzing_parameters/body_schema.py @@ -201,10 +201,10 @@ class BodySchema(): examples = request_block[3] value = None else: - field_name = None value = request_block[1] quoted = request_block[2] examples = request_block[3] + field_name = request_block[4] # accumulate if primitive_type == primitives.STATIC_STRING: diff --git a/restler/engine/primitives.py b/restler/engine/primitives.py index 90228de..91b63e5 100644 --- a/restler/engine/primitives.py +++ b/restler/engine/primitives.py @@ -51,6 +51,11 @@ 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' +# Optional argument passed to fuzzable primitive definition function that can +# provide the name of the parameter being fuzzed. +# This value is used in test-all-combinations mode to allow the user to analyze spec coverage +# for particular parameter values. +PARAM_NAME_ARG = 'param_name' class CandidateValues(object): def __init__(self): self.unquoted_values = [] @@ -374,7 +379,8 @@ def restler_static_string(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_string(*args, **kwargs): @@ -400,7 +406,10 @@ def restler_fuzzable_string(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_int(*args, **kwargs): """ Integer primitive. @@ -425,7 +434,10 @@ def restler_fuzzable_int(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_bool(*args, **kwargs): @@ -451,7 +463,10 @@ def restler_fuzzable_bool(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_number(*args, **kwargs): @@ -477,7 +492,10 @@ def restler_fuzzable_number(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_delim(*args, **kwargs): @@ -503,7 +521,10 @@ def restler_fuzzable_delim(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_group(*args, **kwargs): @@ -536,7 +557,8 @@ def restler_fuzzable_group(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, enum_vals, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, enum_vals, quoted, examples, param_name def restler_fuzzable_uuid4(*args, **kwargs): @@ -562,7 +584,10 @@ def restler_fuzzable_uuid4(*args, **kwargs): examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_datetime(*args, **kwargs) : @@ -589,7 +614,10 @@ def restler_fuzzable_datetime(*args, **kwargs) : examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_fuzzable_object(*args, **kwargs) : """ object primitive ({}) @@ -614,7 +642,9 @@ def restler_fuzzable_object(*args, **kwargs) : examples=None if EXAMPLES_ARG in kwargs: examples = kwargs[EXAMPLES_ARG] - return sys._getframe().f_code.co_name, field_name, quoted, examples + if PARAM_NAME_ARG in kwargs: + param_name = kwargs[PARAM_NAME_ARG] + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_multipart_formdata(*args, **kwargs): """ Multipart/formdata primitive @@ -638,7 +668,8 @@ def restler_multipart_formdata(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_custom_payload(*args, **kwargs): @@ -662,7 +693,8 @@ def restler_custom_payload(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_custom_payload_header(*args, **kwargs): @@ -686,7 +718,8 @@ def restler_custom_payload_header(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_custom_payload_uuid4_suffix(*args, **kwargs): @@ -710,7 +743,8 @@ def restler_custom_payload_uuid4_suffix(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name def restler_refreshable_authentication_token(*args, **kwargs): @@ -734,4 +768,5 @@ def restler_refreshable_authentication_token(*args, **kwargs): if QUOTED_ARG in kwargs: quoted = kwargs[QUOTED_ARG] examples = None - return sys._getframe().f_code.co_name, field_name, quoted, examples + param_name = None + return sys._getframe().f_code.co_name, field_name, quoted, examples, param_name diff --git a/restler/unit_tests/log_baseline_test_files/test_grammar.json b/restler/unit_tests/log_baseline_test_files/test_grammar.json index 227784b..a2f1933 100644 --- a/restler/unit_tests/log_baseline_test_files/test_grammar.json +++ b/restler/unit_tests/log_baseline_test_files/test_grammar.json @@ -137,7 +137,9 @@ "payload": { "Fuzzable": [ "Int", - "1000" + "1000", + null, + "population" ] }, "isRequired": false, diff --git a/restler/unit_tests/log_baseline_test_files/test_grammar.py b/restler/unit_tests/log_baseline_test_files/test_grammar.py index 533d1f3..694997d 100644 --- a/restler/unit_tests/log_baseline_test_files/test_grammar.py +++ b/restler/unit_tests/log_baseline_test_files/test_grammar.py @@ -223,8 +223,12 @@ request = requests.Request([ primitives.restler_static_string("\r\n"), primitives.restler_static_string("{"), primitives.restler_static_string('"population":'), - primitives.restler_fuzzable_int('10000', quoted=True), + primitives.restler_fuzzable_int('10000', quoted=True, param_name="population"), primitives.restler_static_string(', "area": "5000",'), + # Note: There is a discrepancy here due to + # manual creation of this grammar - the fuzzable string + # does not match the grammar.json, which contains + # a constant string for this value. primitives.restler_fuzzable_string('strtest', quoted=True), primitives.restler_static_string(':'), primitives.restler_fuzzable_bool('true', quoted=True), @@ -626,7 +630,7 @@ request = requests.Request([ primitives.restler_static_string("{"), primitives.restler_static_string('testbool', quoted=True), primitives.restler_static_string(':'), - primitives.restler_fuzzable_bool("testval", quoted=True), + primitives.restler_fuzzable_bool("testval", quoted=True, param_name="testbool"), primitives.restler_static_string(',"location":'), primitives.restler_custom_payload("location", quoted=True), primitives.restler_static_string("}"), @@ -849,7 +853,7 @@ request = requests.Request([ primitives.restler_static_string("\r\n"), primitives.restler_static_string("{"), primitives.restler_static_string('"datetest":'), - primitives.restler_fuzzable_datetime("2020-1-1", quoted=True), + primitives.restler_fuzzable_datetime("2020-1-1", quoted=True, example=["2020-2-2"], param_name="datetest"), primitives.restler_static_string(',"id":'), primitives.restler_static_string('"/testparts/'), primitives.restler_custom_payload("testcustomparts", quoted=False), diff --git a/restler/utils/logger.py b/restler/utils/logger.py index 1d77270..b2859ae 100644 --- a/restler/utils/logger.py +++ b/restler/utils/logger.py @@ -480,10 +480,10 @@ def custom_network_logging(sequence, candidate_values_pool, **kwargs): quoted = request_block[2] examples = request_block[3] else: - field_name = None default_val = request_block[1] quoted = request_block[2] examples = request_block[3] + field_name = request_block[4] # Handling dynamic primitives that need fresh rendering every time if primitive == "restler_fuzzable_uuid4": diff --git a/src/compiler/Restler.Compiler.Test/CodeGeneratorTests.fs b/src/compiler/Restler.Compiler.Test/CodeGeneratorTests.fs index d97ec17..e03dac1 100644 --- a/src/compiler/Restler.Compiler.Test/CodeGeneratorTests.fs +++ b/src/compiler/Restler.Compiler.Test/CodeGeneratorTests.fs @@ -5,6 +5,7 @@ namespace Restler.Test open System.IO open Xunit +open System open Restler.Grammar open Restler.CodeGenerator.Python.Types open Tree @@ -23,7 +24,7 @@ module CodeGenerator = /// This is useful because the grammar json is validated /// when deserialized by the RESTler compiler, avoiding typos made directly in python. /// This also sanity checks that the grammar generation is deterministic. - member __.``generate code from modified grammar`` () = + let ``generate code from modified grammar`` () = let grammarOutputDirectory1 = ctx.testRootDirPath Restler.Workflow.generateRestlerGrammar None @@ -42,7 +43,7 @@ module CodeGenerator = [] - member __.``python grammar request sanity test`` () = + let ``python grammar request sanity test`` () = let q1 = LeafNode { LeafProperty.name = "page"; payload = Constant (Int, "1") ;isRequired = true ; isReadOnly = false } let q2 = LeafNode { LeafProperty.name = "page"; payload = Constant (Int, "1") ;isRequired = true ; isReadOnly = false } let b1 = LeafNode { LeafProperty.name = "payload"; payload = Constant (PrimitiveType.String, "hello") ; @@ -88,7 +89,7 @@ module CodeGenerator = Assert.True(elements |> Seq.length > 0) [] - member __.``python grammar parameter sanity test`` () = + let ``python grammar parameter sanity test`` () = let p1 = { LeafProperty.name = "region"; payload = Constant (PrimitiveType.String, "WestUS"); isRequired = true ; isReadOnly = false} let p2 = { LeafProperty.name = "maxResources"; payload = Constant (Int, "10") ; @@ -112,5 +113,34 @@ module CodeGenerator = let hasResources = result |> Seq.tryFind (fun s -> s = (Restler_static_string_constant "\"maxResources\":")) Assert.True(hasResources.IsSome, "resources not found") + // Test that the generated python and json grammars for tracked parameters are correct + [] + let ``tracked parameters tests`` () = + let grammarDirectoryPath = ctx.testRootDirPath + let config = { Restler.Config.SampleConfig with + IncludeOptionalParameters = true + GrammarOutputDirectoryPath = Some grammarDirectoryPath + ResolveBodyDependencies = true + UseBodyExamples = Some false + SwaggerSpecFilePath = Some [(Path.Combine(Environment.CurrentDirectory, @"swagger\example_demo1.json"))] + CustomDictionaryFilePath = Some (Path.Combine(Environment.CurrentDirectory, @"swagger\example_demo_dictionary.json")) + } + Restler.Workflow.generateRestlerGrammar None config + let grammarFilePath = Path.Combine(grammarDirectoryPath, "grammar.py") + let grammar = File.ReadAllText(grammarFilePath) + + // The grammar should not contain gracked parameters by default + Assert.False(grammar.Contains("param_name=")) + + // Now turn on parameter tracking + let config = { config with TrackFuzzedParameterNames = true } + Restler.Workflow.generateRestlerGrammar None config + let grammarFilePath = Path.Combine(grammarDirectoryPath, "grammar.py") + let grammar = File.ReadAllText(grammarFilePath) + + Assert.True(grammar.Contains("param_name=\"storeId\"")) + Assert.True(grammar.Contains("param_name=\"bannedBrands\"")) + Assert.True(grammar.Contains("param_name=\"groceryItemTags\"")) + interface IClassFixture diff --git a/src/compiler/Restler.Compiler/CodeGenerator.fs b/src/compiler/Restler.Compiler/CodeGenerator.fs index 8e1e4f1..80eb626 100644 --- a/src/compiler/Restler.Compiler/CodeGenerator.fs +++ b/src/compiler/Restler.Compiler/CodeGenerator.fs @@ -21,6 +21,7 @@ module Types = defaultValue : string isQuoted : bool exampleValue : string option + trackedParameterName: string option } /// RESTler grammar built-in types @@ -62,18 +63,18 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque match p with | Constant (t,v) -> Restler_static_string_constant v - | Fuzzable (t,v,exv) -> + | Fuzzable (t,v,exv,parameterName) -> match t with - | Bool -> Restler_fuzzable_bool { defaultValue = v ; isQuoted = false ; exampleValue = exv } + | Bool -> Restler_fuzzable_bool { defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName } | PrimitiveType.DateTime -> - Restler_fuzzable_datetime { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv } + Restler_fuzzable_datetime { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } | PrimitiveType.String -> - 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 } + Restler_fuzzable_string { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } + | PrimitiveType.Object -> Restler_fuzzable_object { defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName } + | Int -> Restler_fuzzable_int { defaultValue = v ; isQuoted = false; exampleValue = exv ; trackedParameterName = parameterName } + | Number -> Restler_fuzzable_number { defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName } | Uuid -> - Restler_fuzzable_uuid4 { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv } + Restler_fuzzable_uuid4 { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } | PrimitiveType.Enum (enumPropertyName, _, enumeration, defaultValue) -> let defaultStr = match defaultValue with @@ -86,11 +87,11 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque defaultStr ) Restler_fuzzable_group - { defaultValue = groupValue ; isQuoted = isQuoted ; exampleValue = exv } + { defaultValue = groupValue ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } | Custom c -> match c.payloadType with | CustomPayloadType.String -> - Restler_custom_payload { defaultValue = c.payloadValue ; isQuoted = isQuoted ; exampleValue = None } + Restler_custom_payload { defaultValue = c.payloadValue ; isQuoted = isQuoted ; exampleValue = None ; trackedParameterName = None } | CustomPayloadType.UuidSuffix -> Restler_custom_payload_uuid4_suffix c.payloadValue | CustomPayloadType.Header -> @@ -335,7 +336,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, @@ -427,7 +428,7 @@ let generatePythonFromRequestElement includeOptionalParameters (e:RequestElement ) // Handle the case of '/' if x |> List.isEmpty then - [ Restler_static_string_constant "/" ] + [ Restler_static_string_constant "/" ] else x | HeaderParameters hp -> @@ -826,6 +827,14 @@ let getRequests(requests:Request list) includeOptionalParameters = let quotedStr = sprintf "%s%s%s" exDelim exStr exDelim sprintf ", examples=[%s]" quotedStr + let getTrackedParamPrimitiveParameter paramName = + match paramName with + | None -> "" + | Some str -> + let exStr, exDelim = quoteStringForPythonGrammar str + let quotedStr = sprintf "%s%s%s" exDelim exStr exDelim + sprintf ", param_name=%s" quotedStr + let str = match p with | Restler_static_string_jtoken_delim s -> @@ -856,32 +865,38 @@ let getRequests(requests:Request list) includeOptionalParameters = let quotedDefaultString = sprintf "%s%s%s" delim str delim let exampleParameter = getExamplePrimitiveParameter s.exampleValue - sprintf "primitives.restler_fuzzable_string(%s, quoted=%s%s)" + let trackedParamName = getTrackedParamPrimitiveParameter s.trackedParameterName + sprintf "primitives.restler_fuzzable_string(%s, quoted=%s%s%s)" quotedDefaultString (if s.isQuoted then "True" else "False") exampleParameter + trackedParamName | Restler_fuzzable_group 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)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_fuzzable_number s -> - sprintf "primitives.restler_fuzzable_number(\"%s\"%s)" + sprintf "primitives.restler_fuzzable_number(\"%s\"%s%s)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_fuzzable_bool s -> - sprintf "primitives.restler_fuzzable_bool(\"%s\"%s)" + sprintf "primitives.restler_fuzzable_bool(\"%s\"%s%s)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_fuzzable_datetime s -> - sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s%s)" + sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s%s%s)" s.defaultValue (if s.isQuoted then "True" else "False") (getExamplePrimitiveParameter s.exampleValue) + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_fuzzable_object s -> if String.IsNullOrEmpty s.defaultValue then printfn "ERROR: fuzzable objects should not be empty. Skipping." @@ -891,14 +906,16 @@ let getRequests(requests:Request list) includeOptionalParameters = let quotedDefaultString = sprintf "%s%s%s" delim str delim let exampleParameter = getExamplePrimitiveParameter s.exampleValue - sprintf "primitives.restler_fuzzable_object(%s%s)" + sprintf "primitives.restler_fuzzable_object(%s%s%s)" quotedDefaultString exampleParameter + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_fuzzable_uuid4 s -> - sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s%s)" + sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s%s%s)" s.defaultValue (if s.isQuoted then "True" else "False") (getExamplePrimitiveParameter s.exampleValue) + (getTrackedParamPrimitiveParameter s.trackedParameterName) | Restler_custom_payload p -> sprintf "primitives.restler_custom_payload(\"%s\", quoted=%s)" p.defaultValue diff --git a/src/compiler/Restler.Compiler/Compiler.fs b/src/compiler/Restler.Compiler/Compiler.fs index eb09dbd..786a738 100644 --- a/src/compiler/Restler.Compiler/Compiler.fs +++ b/src/compiler/Restler.Compiler/Compiler.fs @@ -156,7 +156,8 @@ module private Parameters = | _ -> raise (UnsupportedType "Complex path parameters are not supported") let private getParametersFromExample (examplePayload:ExampleRequestPayload) - (parameterList:seq) = + (parameterList:seq) + (trackParameters:bool) = parameterList |> Seq.choose (fun declaredParameter -> // If the declared parameter isn't in the example, skip it. Here, the example is used to @@ -169,7 +170,7 @@ module private Parameters = | PayloadFormat.JToken payloadValue -> let parameterGrammarElement = generateGrammarElementForSchema declaredParameter.ActualSchema - (Some payloadValue, false) [] id + (Some payloadValue, false) trackParameters [] id Some { name = declaredParameter.Name payload = parameterGrammarElement serialization = getParameterSerialization declaredParameter } @@ -205,7 +206,8 @@ module private Parameters = None let pathParameters (swaggerMethodDefinition:OpenApiOperation) (endpoint:string) - (exampleConfig: ExampleRequestPayload list option) = + (exampleConfig: ExampleRequestPayload list option) + (trackParameters:bool) = let declaredPathParameters = swaggerMethodDefinition.ActualParameters |> Seq.filter (fun p -> p.Kind = NSwag.OpenApiParameterKind.Path) @@ -250,19 +252,22 @@ module private Parameters = (tryGetEnumeration schema) (tryGetDefault schema) specExampleValue + trackParameters 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) + getParametersFromExample firstExample (parameter |> stn) trackParameters |> Seq.head |> Some ) ParameterList parameterList let private getParameters (parameterList:seq) - (exampleConfig:ExampleRequestPayload list option) (dataFuzzing:bool) = + (exampleConfig:ExampleRequestPayload list option) + (dataFuzzing:bool) + (trackParameters:bool) = // When data fuzzing is specified, both the full schema and examples should be available for analysis. // Otherwise, use the first example if it exists, or the schema, and return a single schema. @@ -272,9 +277,9 @@ module private Parameters = | Some [] -> None | Some (firstExample::remainingExamples) -> // Use the first example specified to determine the parameter schema and values. - let firstPayload = getParametersFromExample firstExample parameterList + let firstPayload = getParametersFromExample firstExample parameterList trackParameters let restOfPayloads = - remainingExamples |> List.map (fun e -> getParametersFromExample e parameterList) + remainingExamples |> List.map (fun e -> getParametersFromExample e parameterList trackParameters) Some (firstPayload::restOfPayloads) let schemaPayload = @@ -289,15 +294,19 @@ module private Parameters = let parameterPayload = generateGrammarElementForSchema p.ActualSchema - (specExampleValue, true) [] id + (specExampleValue, true) + trackParameters + [] id // Add the name to the parameter payload let parameterPayload = match parameterPayload with | LeafNode leafProperty -> let leafNodePayload = match leafProperty.payload with - | Fuzzable (Enum(propertyName, propertyType, values, defaultValue), x, y) -> - Fuzzable (Enum(p.Name, propertyType, values, defaultValue), x, y) + | Fuzzable (Enum(propertyName, propertyType, values, defaultValue), x, y, z) -> + Fuzzable (Enum(p.Name, propertyType, values, defaultValue), x, y, z) + | Fuzzable (a, b, c, _) -> + Fuzzable (a, b, c, if trackParameters then Some p.Name else None) | _ -> leafProperty.payload LeafNode { leafProperty with payload = leafNodePayload } | InternalNode (internalNode, children) -> @@ -333,7 +342,8 @@ module private Parameters = let getAllParameters (swaggerMethodDefinition:OpenApiOperation) (parameterKind:NSwag.OpenApiParameterKind) - exampleConfig dataFuzzing = + exampleConfig dataFuzzing + trackParameters = let localParameters = swaggerMethodDefinition.ActualParameters |> Seq.filter (fun p -> p.Kind = parameterKind) // add shared parameters for the endpoint, if any @@ -342,7 +352,7 @@ module private Parameters = let allParameters = [localParameters ; declaredSharedParameters ] |> Seq.concat - getParameters allParameters exampleConfig dataFuzzing + getParameters allParameters exampleConfig dataFuzzing trackParameters let generateRequestPrimitives (requestId:RequestId) (responseParser:ResponseParser option) @@ -615,6 +625,7 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list) Parameters.pathParameters m.Value ep (if usePathExamples then exampleConfig else None) + config.TrackFuzzedParameterNames RequestParameters.header = let useHeaderExamples = config.UseHeaderExamples |> Option.defaultValue false @@ -623,7 +634,7 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list) OpenApiParameterKind.Header (if useHeaderExamples then exampleConfig else None) config.DataFuzzing - + config.TrackFuzzedParameterNames RequestParameters.query = let useQueryExamples = config.UseQueryExamples |> Option.defaultValue false @@ -632,6 +643,7 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list) OpenApiParameterKind.Query (if useQueryExamples then exampleConfig else None) config.DataFuzzing + config.TrackFuzzedParameterNames RequestParameters.body = let useBodyExamples = config.UseBodyExamples |> Option.defaultValue false @@ -640,12 +652,13 @@ let generateRequestGrammar (swaggerDocs:Types.ApiSpecFuzzingConfig list) OpenApiParameterKind.Body (if useBodyExamples then exampleConfig else None) config.DataFuzzing + config.TrackFuzzedParameterNames } 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, false) [] id + yield generateGrammarElementForSchema r.Value.ActualResponse.Schema (None, false) false [] id } // 'allResponseProperties' contains the schemas of all possible responses diff --git a/src/compiler/Restler.Compiler/Config.fs b/src/compiler/Restler.Compiler/Config.fs index 791b599..b374eee 100644 --- a/src/compiler/Restler.Compiler/Config.fs +++ b/src/compiler/Restler.Compiler/Config.fs @@ -102,6 +102,11 @@ type Config = // When specified, use only this naming convention to infer // producer-consumer dependencies. ApiNamingConvention : NamingConvention option + + // When this switch is on, the generated grammar will contain + // parameter names for all fuzzable values. For example: + // restler_fuzzable_string("1", param_name="num_items") + TrackFuzzedParameterNames : bool } let convertToAbsPath (currentDirPath:string) (filePath:string) = @@ -209,6 +214,7 @@ let SampleConfig = EngineSettingsFilePath = None DataFuzzing = true ApiNamingConvention = None + TrackFuzzedParameterNames = false } /// The default config used for unit tests. Most of these should also be the defaults for @@ -238,4 +244,5 @@ let DefaultConfig = EngineSettingsFilePath = None DataFuzzing = false ApiNamingConvention = None + TrackFuzzedParameterNames = false } \ No newline at end of file diff --git a/src/compiler/Restler.Compiler/Dependencies.fs b/src/compiler/Restler.Compiler/Dependencies.fs index bd2dd2d..eef1b27 100644 --- a/src/compiler/Restler.Compiler/Dependencies.fs +++ b/src/compiler/Restler.Compiler/Dependencies.fs @@ -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) + | None -> Fuzzable (PrimitiveType.String, "", None, 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, "", None)) + let defaultPayload = (Fuzzable (PrimitiveType.String, "", None, None)) let dependencyPayload = getConsumerPayload dependencies pathPayload requestId parameterName EmptyAccessPath defaultPayload let payloadWithDependencies = diff --git a/src/compiler/Restler.Compiler/Grammar.fs b/src/compiler/Restler.Compiler/Grammar.fs index 686d807..70315fe 100644 --- a/src/compiler/Restler.Compiler/Grammar.fs +++ b/src/compiler/Restler.Compiler/Grammar.fs @@ -135,9 +135,9 @@ type FuzzingPayload = /// Example: (Int "1") | Constant of PrimitiveType * string - /// (data type, default value, example value) + /// (data type, default value, example value, parameter name) /// Example: (Int "1", "2") - | Fuzzable of PrimitiveType * string * string option + | Fuzzable of PrimitiveType * string * string option * string option /// The custom payload, as specified in the fuzzing dictionary | Custom of CustomPayload diff --git a/src/compiler/Restler.Compiler/SwaggerVisitors.fs b/src/compiler/Restler.Compiler/SwaggerVisitors.fs index 66ba5ae..083f61a 100644 --- a/src/compiler/Restler.Compiler/SwaggerVisitors.fs +++ b/src/compiler/Restler.Compiler/SwaggerVisitors.fs @@ -65,8 +65,10 @@ module SchemaUtilities = let getGrammarPrimitiveTypeWithDefaultValue (objectType:NJsonSchema.JsonObjectType) (format:string) - (exampleValue:string option) : - (PrimitiveType * string * string option) = + (exampleValue:string option) + (propertyName : string option) + (trackParameters:bool) : + (PrimitiveType * string * string option * string option) = let defaultTypeWithValue = match objectType with | NJsonSchema.JsonObjectType.String -> @@ -106,10 +108,13 @@ module SchemaUtilities = 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 propertyName = + if trackParameters then propertyName else None + primitiveType, defaultValue, exampleValue, propertyName - let getFuzzableValueForObjectType (objectType:NJsonSchema.JsonObjectType) (format:string) (exampleValue: string option) = - Fuzzable (getGrammarPrimitiveTypeWithDefaultValue objectType format exampleValue) + let getFuzzableValueForObjectType (objectType:NJsonSchema.JsonObjectType) (format:string) (exampleValue: string option) (propertyName: string option) + (trackParameters:bool) = + Fuzzable (getGrammarPrimitiveTypeWithDefaultValue objectType format exampleValue propertyName trackParameters) /// Get a boolean property from 'ExtensionData', if it exists. let getExtensionDataBooleanPropertyValue (extensionData:System.Collections.Generic.IDictionary) (extensionDataKeyName:string) = @@ -129,7 +134,8 @@ open SchemaUtilities module SwaggerVisitors = - let getFuzzableValueForProperty propertyName (propertySchema:NJsonSchema.JsonSchema) isRequired isReadOnly enumeration defaultValue (exampleValue:string option) = + let getFuzzableValueForProperty propertyName (propertySchema:NJsonSchema.JsonSchema) isRequired isReadOnly enumeration defaultValue (exampleValue:string option) + (trackParameters:bool) = let payload = match propertySchema.Type with | NJsonSchema.JsonObjectType.String @@ -137,23 +143,23 @@ module SwaggerVisitors = | NJsonSchema.JsonObjectType.Integer | NJsonSchema.JsonObjectType.Boolean -> match enumeration with - | None -> getFuzzableValueForObjectType propertySchema.Type propertySchema.Format exampleValue + | None -> getFuzzableValueForObjectType propertySchema.Type propertySchema.Format exampleValue (Some propertyName) trackParameters | Some ev -> let enumValues = ev |> Seq.map (fun e -> string e) |> Seq.toList - let grammarPrimitiveType,_,exv = getGrammarPrimitiveTypeWithDefaultValue propertySchema.Type propertySchema.Format exampleValue + let grammarPrimitiveType,_,exv,_ = getGrammarPrimitiveTypeWithDefaultValue propertySchema.Type propertySchema.Format exampleValue (Some propertyName) trackParameters let defaultFuzzableEnumValue = match enumValues with | [] -> "null" | h::rest -> h - Fuzzable (PrimitiveType.Enum (propertyName, grammarPrimitiveType, enumValues, defaultValue), defaultFuzzableEnumValue, exv) + Fuzzable (PrimitiveType.Enum (propertyName, grammarPrimitiveType, enumValues, defaultValue), defaultFuzzableEnumValue, exv, None) | 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 exampleValue + getFuzzableValueForObjectType NJsonSchema.JsonObjectType.Object propertySchema.Format exampleValue (Some propertyName) trackParameters | NJsonSchema.JsonObjectType.File -> // Fuzz it as a string. - Fuzzable (PrimitiveType.String, "file object", None) + Fuzzable (PrimitiveType.String, "file object", None, if trackParameters then Some propertyName else None) | nst -> raise (UnsupportedType (sprintf "Unsupported type formatting: %A" nst)) { LeafProperty.name = propertyName; payload = payload ;isRequired = isRequired ; isReadOnly = isReadOnly } @@ -168,7 +174,23 @@ module SwaggerVisitors = then None else Some (property.Default.ToString()) + /// Add property name to the fuzzable payload + /// This is used to track fuzzed parameter values + let addTrackedParameterName (tree:Tree) paramName trackParameters = + if trackParameters then + match tree with + | LeafNode leafProperty -> + let payload = leafProperty.payload + match payload with + | Fuzzable(a, b, c, _) -> + let payload = Fuzzable (a, b, c, Some paramName) + LeafNode { leafProperty with LeafProperty.payload = payload } + | x -> tree + | InternalNode (_,_) -> tree + else tree + 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) @@ -258,6 +280,7 @@ module SwaggerVisitors = let rec processProperty (propertyName, property:NJsonSchema.JsonSchemaProperty) (propertyPayloadExampleValue: JToken option, generateFuzzablePayload:bool) + (trackParameters:bool) (parents:NJsonSchema.JsonSchema list) (cont: Tree -> Tree) = @@ -285,6 +308,7 @@ module SwaggerVisitors = (tryGetEnumeration propertySchema) (tryGetDefault propertySchema) schemaExampleValue + trackParameters let propertyPayload = match propertyPayloadExampleValue with | None -> @@ -293,13 +317,13 @@ module SwaggerVisitors = | Some v -> let examplePropertyPayload = match fuzzablePropertyPayload.payload with - | Fuzzable (primitiveType, defaultValue, _) -> + | Fuzzable (primitiveType, defaultValue, _, propertyName) -> 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) + Fuzzable (primitiveType, defaultValue, Some payloadValue, propertyName) else Constant (primitiveType, payloadValue) | _ -> raise (invalidOp(sprintf "invalid payload %A, expected fuzzable" fuzzablePropertyPayload)) @@ -312,7 +336,8 @@ module SwaggerVisitors = // The example property value needs to be examined according to the 'ActualSchema', which // is passed through below. - generateGrammarElementForSchema property.ActualSchema (propertyPayloadExampleValue, generateFuzzablePayload) parents (fun tree -> + generateGrammarElementForSchema property.ActualSchema (propertyPayloadExampleValue, generateFuzzablePayload) + trackParameters parents (fun tree -> let innerProperty = { InnerProperty.name = propertyName payload = None propertyType = Property @@ -330,36 +355,47 @@ module SwaggerVisitors = let arrayWithElements = generateGrammarElementForSchema property (propertyPayloadExampleValue, generateFuzzablePayload) - parents (fun tree -> tree) + trackParameters parents (fun tree -> tree) |> cont + // For tracked parameters, it is required to add the name to the fuzzable primitives + // This name will not be added if the array elements are leaf nodes that do not have inner properties. match arrayWithElements with | InternalNode (n, tree) -> + let tree = + tree + |> Seq.map (fun elem -> addTrackedParameterName elem propertyName trackParameters) 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, false) parents (fun tree -> - // If the object has no properties, it should be set to its primitive type. - match tree with - | LeafNode l -> - LeafNode { l with name = propertyName } - |> cont - | InternalNode _ -> - let innerProperty = { InnerProperty.name = propertyName - payload = None - propertyType = Property // indicates presence of nested properties - isRequired = true - isReadOnly = (propertyIsReadOnly property) } - InternalNode (innerProperty, stn tree) - |> cont - ) + let objTree = + // 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, false) trackParameters parents (fun tree -> + // If the object has no properties, it should be set to its primitive type. + match tree with + | LeafNode l -> + LeafNode { l with name = propertyName } + |> cont + | InternalNode _ -> + let innerProperty = { InnerProperty.name = propertyName + payload = None + propertyType = Property // indicates presence of nested properties + isRequired = true + isReadOnly = (propertyIsReadOnly property) } + InternalNode (innerProperty, stn tree) + |> cont + ) + // For tracked parameters, it is required to add the name to the fuzzable primitives + // This name will not be added if the object elements are leaf nodes that do not have inner properties. + let objTree = addTrackedParameterName objTree propertyName trackParameters + objTree | nst -> raise (UnsupportedType (sprintf "Found unsupported type in body parameters: %A" nst)) and generateGrammarElementForSchema (schema:NJsonSchema.JsonSchema) (exampleValue:JToken option, generateFuzzablePayloadsForExamples:bool) + (trackParameters:bool) (parents:NJsonSchema.JsonSchema list) (cont: Tree -> Tree) = @@ -380,7 +416,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, "", None); isRequired = true ; isReadOnly = false } + let leafProperty = { LeafProperty.name = ""; payload = Fuzzable (PrimitiveType.String, "", None, None); isRequired = true ; isReadOnly = false } LeafNode leafProperty |> cont else @@ -395,6 +431,7 @@ module SwaggerVisitors = else Some (processProperty (name, item.Value) (exValue, generateFuzzablePayloadsForExamples) + trackParameters (schema::parents) id)) let arrayProperties = if schema.IsArray then @@ -414,12 +451,13 @@ module SwaggerVisitors = let schemaArrayExamples = ExampleHelpers.tryGetArraySchemaExample schema match schemaArrayExamples with | None -> - generateGrammarElementForSchema schema.Item.ActualSchema (None, false) (schema::parents) id + generateGrammarElementForSchema schema.Item.ActualSchema (None, false) trackParameters (schema::parents) id |> stn | Some sae -> sae |> Seq.map (fun (schemaExampleValue, generateFuzzablePayload) -> generateGrammarElementForSchema schema.Item.ActualSchema (schemaExampleValue, generateFuzzablePayload) + trackParameters (schema::parents) id ) @@ -432,6 +470,7 @@ module SwaggerVisitors = generateGrammarElementForSchema schema.Item.ActualSchema (Some example, false) + trackParameters (schema::parents) id |> stn) |> Seq.concat @@ -440,7 +479,7 @@ module SwaggerVisitors = let allOfParameterSchemas = schema.AllOf - |> Seq.map (fun ao -> ao, generateGrammarElementForSchema ao.ActualSchema (exampleValue, false) (schema::parents) id) + |> Seq.map (fun ao -> ao, generateGrammarElementForSchema ao.ActualSchema (exampleValue, false) trackParameters (schema::parents) id) let allOfProperties = allOfParameterSchemas @@ -496,6 +535,7 @@ module SwaggerVisitors = (tryGetEnumeration schema) (*enumeration*) (tryGetDefault schema) (*defaultValue*) specExampleValue + trackParameters LeafNode leafProperty |> cont | Some v -> @@ -509,13 +549,14 @@ module SwaggerVisitors = let schemaType = if schema.Type = JsonObjectType.None then JsonObjectType.Object else schema.Type - - let primitiveType, defaultValue, _ = getGrammarPrimitiveTypeWithDefaultValue schemaType schema.Format None + // Note: since the paramName is not available here, set it to 'None'. + // This will be fixed up from the parent level. + let primitiveType, defaultValue, _ , _ = getGrammarPrimitiveTypeWithDefaultValue schemaType schema.Format None None trackParameters let leafPayload = let exampleValue = GenerateGrammarElements.formatJTokenProperty primitiveType v if generateFuzzablePayloadsForExamples then - FuzzingPayload.Fuzzable (primitiveType, defaultValue, Some exampleValue) + FuzzingPayload.Fuzzable (primitiveType, defaultValue, Some exampleValue, None) else FuzzingPayload.Constant (primitiveType, exampleValue)