diff --git a/restler/engine/fuzzing_parameters/request_schema_parser.py b/restler/engine/fuzzing_parameters/request_schema_parser.py index 313afbb..58cbc40 100644 --- a/restler/engine/fuzzing_parameters/request_schema_parser.py +++ b/restler/engine/fuzzing_parameters/request_schema_parser.py @@ -181,8 +181,8 @@ def des_param_payload(param_payload_json, tag='', body_param=True): is_dynamic_object = False if 'Fuzzable' in payload: - content_type = payload['Fuzzable'][0] - content_value = payload['Fuzzable'][1] + content_type = payload['Fuzzable']['primitiveType'] + content_value = payload['Fuzzable']['defaultValue'] fuzzable = True elif 'Constant' in payload: content_type = payload['Constant'][0] 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 f34bb14..47bde14 100644 --- a/restler/unit_tests/log_baseline_test_files/test_grammar.json +++ b/restler/unit_tests/log_baseline_test_files/test_grammar.json @@ -136,12 +136,11 @@ "LeafNode": { "name": "population", "payload": { - "Fuzzable": [ - "Int", - "1000", - null, - "population" - ] + "Fuzzable": { + "primitiveType": "Int", + "defaultValue": "1000", + "parameterName": "population" + } }, "isRequired": false, "isReadOnly": false @@ -164,10 +163,11 @@ "LeafNode": { "name": "strtest", "payload": { - "Fuzzable": [ - "String", - "true" - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "true", + "parameterName": "population" + } }, "isRequired": true, "isReadOnly": false @@ -247,8 +247,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "group", "String", @@ -260,8 +260,8 @@ null ] }, - "A" - ] + "defaultValue": "A" + } } } } @@ -482,8 +482,8 @@ "LeafNode": { "name": "group", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "group", "String", @@ -495,8 +495,8 @@ null ] }, - "A" - ] + "defaultValue": "A" + } } } } @@ -1524,11 +1524,11 @@ "LeafNode": { "name": "testbool", "payload": { - "Fuzzable": [ - "Bool", - "testval", - false - ] + "Fuzzable": { + "primitiveType": "Bool", + "defaultValue": "testval", + "exampleValue": "false" + } }, "isRequired": true, "isReadOnly": false @@ -2150,10 +2150,10 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - "DateTime", - "2020-1-1" - ] + "Fuzzable": { + "primitiveType": "DateTime", + "defaultValue": "2020-1-1" + } }, "isRequired": true, "isReadOnly": false @@ -2185,10 +2185,10 @@ "LeafNode": { "name": "datetest", "payload": { - "Fuzzable": [ - "DateTime", - "2020-1-1" - ] + "Fuzzable": { + "primitiveType": "DateTime", + "defaultValue": "2020-1-1" + } }, "isRequired": false, "isReadOnly": false diff --git a/restler/unit_tests/log_baseline_test_files/test_grammar_body.json b/restler/unit_tests/log_baseline_test_files/test_grammar_body.json index 65e7f54..7a8c9e0 100644 --- a/restler/unit_tests/log_baseline_test_files/test_grammar_body.json +++ b/restler/unit_tests/log_baseline_test_files/test_grammar_body.json @@ -114,10 +114,10 @@ "LeafNode": { "name": "population", "payload": { - "Fuzzable": [ - "Int", - "1000" - ] + "Fuzzable": { + "primitiveType": "Int", + "defaultValue": "1000" + } }, "isRequired": false, "isReadOnly": false @@ -140,10 +140,10 @@ "LeafNode": { "name": "strtest", "payload": { - "Fuzzable": [ - "String", - "true" - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "true" + } }, "isRequired": true, "isReadOnly": false @@ -171,10 +171,10 @@ "LeafNode": { "name": "subtest", "payload": { - "Fuzzable": [ - "Bool", - "true" - ] + "Fuzzable": { + "primitiveType": "Bool", + "defaultValue": "true" + } }, "isRequired": false, "isReadOnly": false @@ -265,8 +265,10 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + + "Fuzzable": { + "primitiveType": + { "Enum": [ "group", "String", @@ -278,8 +280,8 @@ null ] }, - "A" - ] + "defaultValue": "A" + } } } } @@ -508,7 +510,8 @@ "LeafNode": { "name": "group", "payload": { - "Fuzzable": [ + "Fuzzable": { + "primitiveType": { "Enum": [ "group", @@ -521,8 +524,8 @@ null ] }, - "A" - ] + "defaultValue": "A" + } } } } diff --git a/src/compiler/Restler.Compiler.Test/DependencyTests.fs b/src/compiler/Restler.Compiler.Test/DependencyTests.fs index 570cb4e..e2d8f42 100644 --- a/src/compiler/Restler.Compiler.Test/DependencyTests.fs +++ b/src/compiler/Restler.Compiler.Test/DependencyTests.fs @@ -324,7 +324,7 @@ module Dependencies = ResolveBodyDependencies = true UseBodyExamples = Some true SwaggerSpecFilePath = Some [(Path.Combine(Environment.CurrentDirectory, @"swagger\dependencyTests\input_producer_spec.json"))] - CustomDictionaryFilePath = None + CustomDictionaryFilePath = Some (Path.Combine(Environment.CurrentDirectory, @"swagger\dependencyTests\input_producer_dict.json")) AnnotationFilePath = Some (Path.Combine(Environment.CurrentDirectory, @"swagger\dependencyTests\input_producer_annotations.json")) AllowGetProducers = true } @@ -337,6 +337,20 @@ module Dependencies = Assert.True(grammar.Contains("""restler_custom_payload_uuid4_suffix("fileId", writer=_file__fileId__post_fileId_path.writer())""")) Assert.True(grammar.Contains("""restler_static_string(_file__fileId__post_fileId_path.reader(), quoted=False)""")) + // Validate (tag, label) annotation. tag - body producer (jsonpath), label: path parameter. + Assert.True(grammar.Contains("""primitives.restler_custom_payload("tag", quoted=True, writer=_archive_post_tag.writer())""")) + Assert.True(grammar.Contains("""restler_static_string(_archive_post_tag.reader(), quoted=True)""")) + + // Validate (name, name) annotation. name - body producer (POST) and consumer (PUT). + Assert.True(grammar.Contains("""primitives.restler_fuzzable_object("{ \"fuzz\": false }", writer=_archive_post_name.writer())""")) + Assert.True(grammar.Contains("""primitives.restler_static_string(_archive_post_name.reader(), quoted=False)""")) + + // Validate (hash, sig) annotation. hash - header producer (POST), sig - header consumer (PUT) + Assert.True(grammar.Contains("""primitives.restler_custom_payload_query("hash", writer=_archive_post_hash_query.writer())""")) + Assert.True(grammar.Contains("""primitives.restler_static_string(_archive_post_hash_query.reader(), quoted=False)""")) + + + /// Test that the entire body should be able to be replaced with a custom payload /// from the dictionary diff --git a/src/compiler/Restler.Compiler.Test/Restler.Compiler.Test.fsproj b/src/compiler/Restler.Compiler.Test/Restler.Compiler.Test.fsproj index 7ce3ee4..2beacc1 100644 --- a/src/compiler/Restler.Compiler.Test/Restler.Compiler.Test.fsproj +++ b/src/compiler/Restler.Compiler.Test/Restler.Compiler.Test.fsproj @@ -76,6 +76,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/compiler/Restler.Compiler.Test/baselines/grammarTests/required_params_grammar.json b/src/compiler/Restler.Compiler.Test/baselines/grammarTests/required_params_grammar.json index 5b2444c..170212d 100644 --- a/src/compiler/Restler.Compiler.Test/baselines/grammarTests/required_params_grammar.json +++ b/src/compiler/Restler.Compiler.Test/baselines/grammarTests/required_params_grammar.json @@ -43,12 +43,10 @@ "LeafNode": { "name": "name", "payload": { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } }, "isRequired": false, "isReadOnly": false @@ -58,12 +56,10 @@ "LeafNode": { "name": "tags", "payload": { - "Fuzzable": [ - "Object", - "{ \"fuzz\": false }", - null, - null - ] + "Fuzzable": { + "primitiveType": "Object", + "defaultValue": "{ \"fuzz\": false }" + } }, "isRequired": false, "isReadOnly": false @@ -159,12 +155,10 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } }, "isRequired": false, "isReadOnly": false @@ -198,12 +192,10 @@ "LeafNode": { "name": "name", "payload": { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } }, "isRequired": true, "isReadOnly": false @@ -213,12 +205,10 @@ "LeafNode": { "name": "tags", "payload": { - "Fuzzable": [ - "Object", - "{ \"fuzz\": false }", - null, - null - ] + "Fuzzable": { + "primitiveType": "Object", + "defaultValue": "{ \"fuzz\": false }" + } }, "isRequired": false, "isReadOnly": false @@ -246,12 +236,10 @@ "LeafNode": { "name": "name", "payload": { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } }, "isRequired": false, "isReadOnly": false @@ -261,12 +249,10 @@ "LeafNode": { "name": "id", "payload": { - "Fuzzable": [ - "Number", - "1.23", - null, - null - ] + "Fuzzable": { + "primitiveType": "Number", + "defaultValue": "1.23" + } }, "isRequired": true, "isReadOnly": false @@ -306,12 +292,10 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } }, "isRequired": false, "isReadOnly": false diff --git a/src/compiler/Restler.Compiler.Test/baselines/schemaTests/xMsPaths_grammar.json b/src/compiler/Restler.Compiler.Test/baselines/schemaTests/xMsPaths_grammar.json index 27dfc33..a7b5af3 100644 --- a/src/compiler/Restler.Compiler.Test/baselines/schemaTests/xMsPaths_grammar.json +++ b/src/compiler/Restler.Compiler.Test/baselines/schemaTests/xMsPaths_grammar.json @@ -70,8 +70,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "api-version", "String", @@ -81,10 +81,8 @@ null ] }, - "2020-03-01", - null, - null - ] + "defaultValue": "2020-03-01" + } }, "isRequired": true, "isReadOnly": false @@ -215,8 +213,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "api-version", "String", @@ -226,10 +224,8 @@ null ] }, - "2020-03-01", - null, - null - ] + "defaultValue": "2020-03-01" + } }, "isRequired": true, "isReadOnly": false @@ -381,8 +377,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "api-version", "String", @@ -392,10 +388,8 @@ null ] }, - "2020-03-01", - null, - null - ] + "defaultValue": "2020-03-01" + } }, "isRequired": true, "isReadOnly": false @@ -443,12 +437,10 @@ } }, { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } } ], "queryParameters": [ @@ -478,8 +470,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "api-version", "String", @@ -489,10 +481,8 @@ null ] }, - "2020-03-01", - null, - null - ] + "defaultValue": "2020-03-01" + } }, "isRequired": true, "isReadOnly": false @@ -540,12 +530,10 @@ } }, { - "Fuzzable": [ - "String", - "fuzzstring", - null, - null - ] + "Fuzzable": { + "primitiveType": "String", + "defaultValue": "fuzzstring" + } } ], "queryParameters": [ @@ -575,8 +563,8 @@ "LeafNode": { "name": "", "payload": { - "Fuzzable": [ - { + "Fuzzable": { + "primitiveType": { "Enum": [ "api-version", "String", @@ -586,10 +574,8 @@ null ] }, - "2020-03-01", - null, - null - ] + "defaultValue": "2020-03-01" + } }, "isRequired": true, "isReadOnly": false diff --git a/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_annotations.json b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_annotations.json index ad78ddf..749a26c 100644 --- a/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_annotations.json +++ b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_annotations.json @@ -5,6 +5,30 @@ "producer_method": "POST", "producer_resource_name": "fileId", "consumer_param": "fileId" + }, + { + "producer_endpoint": "/archive", + "producer_method": "POST", + "producer_resource_name": "hash", + "consumer_param": "sig" + }, + { + "producer_endpoint": "/archive", + "producer_method": "POST", + "producer_resource_name": "/tag", + "consumer_param": "label" + }, + { + "producer_endpoint": "/archive", + "producer_method": "POST", + "producer_resource_name": "/tag", + "consumer_param": "/tag" + }, + { + "producer_endpoint": "/archive", + "producer_method": "POST", + "producer_resource_name": "/name", + "consumer_param": "/name" } ] } diff --git a/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_dict.json b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_dict.json new file mode 100644 index 0000000..7c3c021 --- /dev/null +++ b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_dict.json @@ -0,0 +1,9 @@ +{ + "restler_custom_payload": { + "sig": [ "12345" ], + "tag": ["important"] + }, + "restler_custom_payload_query": { + "hash": [ "56789" ] + } +} \ No newline at end of file diff --git a/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_spec.json b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_spec.json index 0c88aa0..84b9b2b 100644 --- a/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_spec.json +++ b/src/compiler/Restler.Compiler.Test/swagger/dependencyTests/input_producer_spec.json @@ -13,10 +13,98 @@ "fileId":{ "type": "String", "description": "the file id" - + }, + "Archive": { + "properties": { + "name": { + "type": "object" + }, + "tag": { + "type": "string" + } + } } }, "paths": { + "/archive/{archiveId}/{label}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "archiveId", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "label", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + }, + "/archive": { + "post": { + "parameters": [ + { + "in": "query", + "name": "hash", + "required": true, + "type": "number" + }, + { + "in": "body", + "name": "bodyParam", + "required": true, + "schema": { + "$ref": "#/definitions/Archive" + } + } + ], + "responses": { + "201": { + "description": "Success" + } + } + } + }, + "/archive/{archiveId}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "archiveId", + "required": true, + "type": "String" + }, + { + "in": "query", + "name": "sig", + "required": true, + "type": "number" + }, + { + "in": "body", + "name": "bodyParam", + "required": true, + "schema": { + "$ref": "#/definitions/Archive" + } + } + ], + "responses": { + "201": { + "description": "Success" + } + } + } + }, "/file/{fileId}": { "post": { "parameters": [ diff --git a/src/compiler/Restler.Compiler/ApiResourceTypes.fs b/src/compiler/Restler.Compiler/ApiResourceTypes.fs index ea22b6c..52fe80e 100644 --- a/src/compiler/Restler.Compiler/ApiResourceTypes.fs +++ b/src/compiler/Restler.Compiler/ApiResourceTypes.fs @@ -372,7 +372,10 @@ type Producer = /// Currently, only assigning such values from the dictionary is supported. /// The dictionary payload is an option type because it is only present when /// the initial payload is being generated. - | InputParameter of InputOnlyProducer * DictionaryPayload option + /// (producer, dictionary payload, isWriter) + /// When 'isWriter' is true, this is a writer variable that should be generated + /// with the original payload. + | InputParameter of InputOnlyProducer * DictionaryPayload option * bool | OrderingConstraintParameter of OrderingConstraintProducer diff --git a/src/compiler/Restler.Compiler/CodeGenerator.fs b/src/compiler/Restler.Compiler/CodeGenerator.fs index 99db0fe..3e0cabf 100644 --- a/src/compiler/Restler.Compiler/CodeGenerator.fs +++ b/src/compiler/Restler.Compiler/CodeGenerator.fs @@ -24,28 +24,31 @@ module Types = trackedParameterName: string option } + type DynamicObjectWriter = + | DynamicObjectWriter of string + /// RESTler grammar built-in types /// IMPORTANT ! All primitives must be supported in restler/engine/primitives.py type RequestPrimitiveType = | Restler_static_string_constant of string | Restler_static_string_variable of string * bool | Restler_static_string_jtoken_delim of string - | Restler_fuzzable_string of RequestPrimitiveTypeData - | Restler_fuzzable_datetime of RequestPrimitiveTypeData - | Restler_fuzzable_date of RequestPrimitiveTypeData - | Restler_fuzzable_object of RequestPrimitiveTypeData + | Restler_fuzzable_string of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_datetime of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_date of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_object of RequestPrimitiveTypeData * DynamicObjectWriter option | Restler_fuzzable_delim of RequestPrimitiveTypeData - | Restler_fuzzable_uuid4 of RequestPrimitiveTypeData - | Restler_fuzzable_group of RequestPrimitiveTypeData - | Restler_fuzzable_bool of RequestPrimitiveTypeData - | Restler_fuzzable_int of RequestPrimitiveTypeData - | Restler_fuzzable_number of RequestPrimitiveTypeData + | Restler_fuzzable_uuid4 of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_group of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_bool of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_int of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_fuzzable_number of RequestPrimitiveTypeData * DynamicObjectWriter option | Restler_multipart_formdata of string - | Restler_custom_payload of RequestPrimitiveTypeData - | Restler_custom_payload_header of string - | Restler_custom_payload_query of string + | Restler_custom_payload of RequestPrimitiveTypeData * DynamicObjectWriter option + | Restler_custom_payload_header of string * DynamicObjectWriter option + | Restler_custom_payload_query of string * DynamicObjectWriter option /// (Payload name, dynamic object writer name) - | Restler_custom_payload_uuid4_suffix of string * string option + | Restler_custom_payload_uuid4_suffix of string * DynamicObjectWriter option | Restler_refreshable_authentication_token of string | Restler_basepath of string | Shadow_values of string @@ -72,20 +75,27 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque match p with | Constant (t,v) -> Restler_static_string_constant v - | Fuzzable (t,v,exv,parameterName) -> - match t with - | Bool -> Restler_fuzzable_bool { defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName } + | Fuzzable fp -> + let v = fp.defaultValue + let exv = fp.exampleValue + let parameterName = fp.parameterName + let dynamicObject = if fp.dynamicObject.IsSome then Some (DynamicObjectWriter fp.dynamicObject.Value.variableName) else None + match fp.primitiveType with + | Bool -> + Restler_fuzzable_bool ({ defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) | PrimitiveType.DateTime -> - Restler_fuzzable_datetime { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } + Restler_fuzzable_datetime ({ defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) | PrimitiveType.Date -> - Restler_fuzzable_date { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } + Restler_fuzzable_date ({ defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) + | PrimitiveType.String -> - 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 } + Restler_fuzzable_string ({ defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) + | PrimitiveType.Object -> Restler_fuzzable_object ({ defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) + | Int -> Restler_fuzzable_int ({ defaultValue = v ; isQuoted = false; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) + + | Number -> Restler_fuzzable_number ({ defaultValue = v ; isQuoted = false ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) | Uuid -> - Restler_fuzzable_uuid4 { defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } + Restler_fuzzable_uuid4 ({ defaultValue = v ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) | PrimitiveType.Enum (enumPropertyName, _, enumeration, defaultValue) -> let defaultStr = match defaultValue with @@ -97,18 +107,19 @@ let rec getRestlerPythonPayload (payload:FuzzingPayload) (isQuoted:bool) : Reque (enumeration |> List.map (fun s -> sprintf "'%s'" s) |> String.concat ",") defaultStr ) - Restler_fuzzable_group - { defaultValue = groupValue ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName } + Restler_fuzzable_group ( + { defaultValue = groupValue ; isQuoted = isQuoted ; exampleValue = exv ; trackedParameterName = parameterName }, dynamicObject) | Custom c -> + let dynamicObject = if c.dynamicObject.IsSome then Some (DynamicObjectWriter c.dynamicObject.Value.variableName) else None match c.payloadType with | CustomPayloadType.String -> - Restler_custom_payload { defaultValue = c.payloadValue ; isQuoted = isQuoted ; exampleValue = None ; trackedParameterName = None } + Restler_custom_payload ({ defaultValue = c.payloadValue ; isQuoted = isQuoted ; exampleValue = None ; trackedParameterName = None }, dynamicObject) | CustomPayloadType.UuidSuffix -> - Restler_custom_payload_uuid4_suffix (c.payloadValue, if c.dynamicObject.IsSome then Some c.dynamicObject.Value.variableName else None) + Restler_custom_payload_uuid4_suffix (c.payloadValue, dynamicObject) | CustomPayloadType.Header -> - Restler_custom_payload_header c.payloadValue // TODO: need test + Restler_custom_payload_header (c.payloadValue, dynamicObject) | CustomPayloadType.Query -> - Restler_custom_payload_query c.payloadValue // TODO: need test + Restler_custom_payload_query (c.payloadValue, dynamicObject) | DynamicObject dv -> Restler_static_string_variable (sprintf "%s.reader()" dv.variableName, isQuoted) | PayloadParts p -> @@ -350,10 +361,10 @@ let generatePythonParameter includeOptionalParameters parameterKind (requestPara | FuzzingPayload.Constant (primitiveType, v) -> isPrimitiveTypeQuoted primitiveType (isNull v), false, false - | FuzzingPayload.Fuzzable (primitiveType, _, _,_) -> + | FuzzingPayload.Fuzzable fp -> // Note: this is a current RESTler limitation - // fuzzable values may not be set to null without changing the grammar. - isPrimitiveTypeQuoted primitiveType false, + isPrimitiveTypeQuoted fp.primitiveType false, true, false | FuzzingPayload.DynamicObject dv -> isPrimitiveTypeQuoted dv.primitiveType false, @@ -1065,6 +1076,12 @@ let getRequests(requests:Request list) includeOptionalParameters = let quotedStr = sprintf "%s%s%s" exDelim exStr exDelim sprintf ", param_name=%s" quotedStr + let formatDynamicObjectVariable (dynamicObject:DynamicObjectWriter option) = + match dynamicObject with + | None -> "" + | Some (DynamicObjectWriter v) -> + sprintf ", writer=%s.writer()" v + let str = match p with | Restler_static_string_jtoken_delim s -> @@ -1088,7 +1105,7 @@ let getRequests(requests:Request list) includeOptionalParameters = sprintf "primitives.restler_static_string(%s, quoted=%s)" s (if isQuoted then "True" else "False") - | Restler_fuzzable_string s -> + | Restler_fuzzable_string (s, dynamicObject) -> if String.IsNullOrEmpty s.defaultValue then printfn "ERROR: fuzzable strings should not be empty. Skipping." "" @@ -1103,39 +1120,44 @@ let getRequests(requests:Request list) includeOptionalParameters = (if s.isQuoted then "True" else "False") exampleParameter trackedParamName - | Restler_fuzzable_group s -> + | Restler_fuzzable_group (s, dynamicObject) -> 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%s)" + | Restler_fuzzable_int (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_int(\"%s\"%s%s%s)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_number s -> - sprintf "primitives.restler_fuzzable_number(\"%s\"%s%s)" + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_number (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_number(\"%s\"%s%s%s)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_bool s -> - sprintf "primitives.restler_fuzzable_bool(\"%s\"%s%s)" + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_bool (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_bool(\"%s\"%s%s%s)" s.defaultValue (getExamplePrimitiveParameter s.exampleValue) (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_datetime s -> - sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s%s%s)" + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_datetime (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_datetime(\"%s\", quoted=%s%s%s%s)" s.defaultValue (if s.isQuoted then "True" else "False") (getExamplePrimitiveParameter s.exampleValue) (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_date s -> - sprintf "primitives.restler_fuzzable_date(\"%s\", quoted=%s%s%s)" + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_date (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_date(\"%s\", quoted=%s%s%s%s)" s.defaultValue (if s.isQuoted then "True" else "False") (getExamplePrimitiveParameter s.exampleValue) (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_object s -> + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_object (s, dynamicObject) -> if String.IsNullOrEmpty s.defaultValue then printfn "ERROR: fuzzable objects should not be empty. Skipping." "" @@ -1144,31 +1166,35 @@ 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%s)" + sprintf "primitives.restler_fuzzable_object(%s%s%s%s)" quotedDefaultString exampleParameter (getTrackedParamPrimitiveParameter s.trackedParameterName) - | Restler_fuzzable_uuid4 s -> - sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s%s%s)" + (formatDynamicObjectVariable dynamicObject) + | Restler_fuzzable_uuid4 (s, dynamicObject) -> + sprintf "primitives.restler_fuzzable_uuid4(\"%s\", quoted=%s%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)" + (formatDynamicObjectVariable dynamicObject) + | Restler_custom_payload (p, dynamicObject) -> + sprintf "primitives.restler_custom_payload(\"%s\", quoted=%s%s)" p.defaultValue (if p.isQuoted then "True" else "False") - | Restler_custom_payload_uuid4_suffix (p, variableName) -> - let variableNamePart = - match variableName with - | None -> "" - | Some vn -> - sprintf ", writer=%s.writer()" vn - sprintf "primitives.restler_custom_payload_uuid4_suffix(\"%s\"%s)" p variableNamePart - | Restler_custom_payload_header p -> - sprintf "primitives.restler_custom_payload_header(\"%s\")" p - | Restler_custom_payload_query q -> - sprintf "primitives.restler_custom_payload_query(\"%s\")" q + (formatDynamicObjectVariable dynamicObject) + | Restler_custom_payload_uuid4_suffix (p, dynamicObject) -> + sprintf "primitives.restler_custom_payload_uuid4_suffix(\"%s\"%s)" + p + (formatDynamicObjectVariable dynamicObject) + | Restler_custom_payload_header (p, dynamicObject) -> + sprintf "primitives.restler_custom_payload_header(\"%s\"%s)" + p + (formatDynamicObjectVariable dynamicObject) + | Restler_custom_payload_query (q, dynamicObject) -> + sprintf "primitives.restler_custom_payload_query(\"%s\"%s)" + q + (formatDynamicObjectVariable dynamicObject) | Restler_refreshable_authentication_token tok -> sprintf "primitives.restler_refreshable_authentication_token(\"%s\")" tok | Restler_basepath bp -> diff --git a/src/compiler/Restler.Compiler/Compiler.fs b/src/compiler/Restler.Compiler/Compiler.fs index c9bf8dc..badd86b 100644 --- a/src/compiler/Restler.Compiler/Compiler.fs +++ b/src/compiler/Restler.Compiler/Compiler.fs @@ -52,7 +52,7 @@ type UserSpecifiedRequestConfig = let getWriterVariable (producer:Producer) (kind:DynamicObjectVariableKind) = match producer with - | InputParameter (iop, _) -> + | InputParameter (iop, _, _) -> { requestId = iop.id.RequestId accessPathParts = iop.getInputParameterAccessPath() @@ -133,7 +133,7 @@ let getResponseParsers (dependencies:seq) (orderingC match ro.id.ResourceReference with | HeaderResource _ -> Some DynamicObjectVariableKind.Header | _ -> Some DynamicObjectVariableKind.BodyResponseProperty - | InputParameter (_, _) -> + | InputParameter (_, _,_) -> Some DynamicObjectVariableKind.InputParameter | _ -> None match writerVariableKind with @@ -496,11 +496,16 @@ module private Parameters = match parameterPayload with | LeafNode leafProperty -> let leafNodePayload = + match leafProperty.payload with - | 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) + | Fuzzable fp -> + match fp.primitiveType with + | Enum(propertyName, propertyType, values, defaultValue) -> + let primitiveType = PrimitiveType.Enum(p.Name, propertyType, values, defaultValue) + Fuzzable { fp with primitiveType = primitiveType } + | _ -> + Fuzzable {fp with + parameterName = if trackParameters then Some p.Name else None } | _ -> leafProperty.payload LeafNode { leafProperty with payload = leafNodePayload } | InternalNode (internalNode, children) -> diff --git a/src/compiler/Restler.Compiler/Dependencies.fs b/src/compiler/Restler.Compiler/Dependencies.fs index 446fc93..a3f9097 100644 --- a/src/compiler/Restler.Compiler/Dependencies.fs +++ b/src/compiler/Restler.Compiler/Dependencies.fs @@ -463,27 +463,16 @@ let findProducerWithResourceName else Seq.empty - // If the consumer is exactly the same as the producer, then check if - // a custom payload is defined in the dictionary. If so, return the empty seq - // (the custom payload will be picked up later), and if not create a custom UUID suffix let dictionary, inputOnlyProducer = match inputOnlyProducers |> Seq.tryHead with | None -> dictionary, None | Some iop -> if annotationProducerRequestId = consumer.id.RequestId then - // This is when the value is written - if dictionary.restler_custom_payload.Value.ContainsKey(consumerResourceName) then - dictionary, None // TODO: support custom payloads - else - let dictionary, suffixProducer = addUuidSuffixEntryForConsumer consumerResourceName dictionary consumer.id - match suffixProducer with - | Some (DictionaryPayload dp) -> - dictionary, Some (InputParameter(iop, Some dp)) - | _ -> - raise (invalidOp("a suffix entry must be a dictionary payload")) + // This is the writer. This will be handled later. + dictionary, None else // Read the value - no associated dictionary payload - dictionary, Some (InputParameter(iop, None)) + dictionary, Some (InputParameter(iop, None, false)) let annotationProducer = [ responseProducers @@ -523,6 +512,56 @@ let findProducerWithResourceName ] |> Seq.concat + + // Check if this consumer is a writer for an input-only producer. + // If yes, this information must be added to the dictionary matches (if any). + let dictionary, inputProducerMatches = + match annotationProducer with + | Some _ -> dictionary, Seq.empty + | None -> + // Find the input-only producer corresponding to this consumer + let matchingInputOnlyProducers = + producers.getInputOnlyProducers(consumer.id.ResourceName) + let inputOnlyProducers = + matchingInputOnlyProducers + |> Seq.filter (fun p -> + p.id.RequestId = consumer.id.RequestId && + p.id.ResourceReference = consumer.id.ResourceReference) + + match inputOnlyProducers |> Seq.tryHead with + | Some iop -> + match [ dictionaryMatches ; uuidSuffixDictionaryMatches ] |> Seq.concat |> Seq.tryHead with + | Some p -> + // Add the dictionary payload + let dictionaryPayload = + match p with + | DictionaryPayload dp -> dp + | _ -> raise (invalidArg "dictionaryMatches" "A dictionary payload was expected.") + dictionary, InputParameter(iop, Some dictionaryPayload, true) |> stn + | None -> + // By default, create a custom_payload_uuid_suffix, so a different ID will be + // generated each time the value is written. + // TODO: This logic will only work for string types (e.g. names). It does not currently work for other types that + // need a unique value assigned (e.g. GUIDs and integers). + // Once a type is introduced for unique GUIDs and integers, this logic should be modified to create an + // appropriate dictionary entry that will direct RESTler to automatically generate unique values. + // + // If a dictionary payload is not returned below, a fuzzable payload will be generated as usual, and the + // fuzzed value will be asssigned to the producer (writer variable). + match consumer.id.PrimitiveType with + | PrimitiveType.String -> + let dictionary, suffixProducer = + addUuidSuffixEntryForConsumer consumerResourceName dictionary consumer.id + match suffixProducer with + | Some (DictionaryPayload dp) -> + dictionary, InputParameter(iop, Some dp, true) |> stn + | _ -> + raise (invalidOp("a suffix entry must be a dictionary payload")) + | _ -> + dictionary, InputParameter(iop, None, true) |> stn + | None -> + dictionary, Seq.empty + // Here the producers just match on the resource name. If the container also matches, // it will match fully. let inferredExactMatches = matchingResourceProducersByEndpoint @@ -618,6 +657,7 @@ let findProducerWithResourceName else dictionary, [ if annotationProducer.IsSome then stn annotationProducer.Value else Seq.empty + inputProducerMatches dictionaryMatches uuidSuffixDictionaryMatches inferredExactMatches @@ -783,7 +823,7 @@ let findAnnotation globalAnnotations let getPayloadPrimitiveType (payload:FuzzingPayload) = match payload with | Constant (t,_) -> t - | Fuzzable (t,_,_,_) -> t + | Fuzzable fp -> fp.primitiveType | Custom cp -> cp.primitiveType | DynamicObject d -> d.primitiveType | PayloadParts _ -> @@ -884,7 +924,7 @@ let getParameterDependencies parameterKind globalAnnotations let resourceAccessPath = PropertyAccessPaths.getLeafAccessPath parentAccessPath p let primitiveType = match p.payload with - | FuzzingPayload.Fuzzable (pt, _, _,_) -> Some pt + | FuzzingPayload.Fuzzable fp -> Some fp.primitiveType | FuzzingPayload.Constant (pt, _) -> Some pt | FuzzingPayload.Custom c -> Some c.primitiveType | _ -> None @@ -958,47 +998,52 @@ let createHeaderResponseProducer (requestId:RequestId) (headerParameterName:stri let createInputOnlyProducerFromAnnotation (a:ProducerConsumerAnnotation) (pathConsumers:(RequestId * seq) []) (queryConsumers:(RequestId * seq) []) - (bodyConsumers:(RequestId * seq) []) = + (bodyConsumers:(RequestId * seq) []) + (headerConsumers:(RequestId * seq) [])= + + let findConsumer (candidateConsumers:(RequestId * seq) []) resourceName = + let c = candidateConsumers + |> Array.tryFind (fun (reqId, _) -> reqId = a.producerId) + + match c with + | None -> + // The consumer for the parameter was not found. + None + | Some (req, consumers) -> + consumers + |> Seq.tryFind (fun c -> c.id.ResourceName = resourceName) + // Find the producer in the list of consumers match a.producerParameter.Value with | ResourceName rn -> - if a.producerId.endpoint.Contains(sprintf "{%s}" rn) then - // Look for the corresponding path producer. - // This is needed to determine its type. - let pc = pathConsumers - |> Array.tryFind (fun (reqId, s) -> reqId = a.producerId) + let parameterKind, consumer = + if a.producerId.endpoint.Contains(sprintf "{%s}" rn) then + let pathConsumer = findConsumer pathConsumers rn + ParameterKind.Path, pathConsumer + else + // Check if this parameter name exists in the query or header. + let queryConsumer = findConsumer queryConsumers rn + if queryConsumer.IsSome then + ParameterKind.Query, queryConsumer + else + ParameterKind.Header, findConsumer headerConsumers rn - let pathConsumer = - match pc with - | None -> - // The consumer for the path parameter was not found. - None - | Some (req, consumers) -> - consumers - |> Seq.tryFind (fun c -> c.id.ResourceName = rn) + match consumer with + | None -> None + | Some c -> + // 'c' is the consumer that is the input producer. + // Its value will be written to a writer variable. + let resource = ApiResource(c.id.RequestId, + c.id.ResourceReference, + c.id.NamingConvention, + c.id.PrimitiveType) + let inputOnlyProducer = + { + InputOnlyProducer.id = resource + parameterKind = parameterKind + } + Some (rn, inputOnlyProducer) - match pathConsumer with - | None -> - // The consumer for the path parameter was not found. - None - | Some c -> - let resource = ApiResource(c.id.RequestId, - c.id.ResourceReference, - c.id.NamingConvention, - c.id.PrimitiveType) - let inputOnlyProducer = - { - InputOnlyProducer.id = resource - parameterKind = ParameterKind.Path - } - Some (rn, inputOnlyProducer) - else - // TODO: add query and header support - // Note: here, it is ambiguous whether this resource refers to a - // query or body resource without specifying the path. For a producer - // id, it should be required to specify a path to a body parameter. - printfn "Warning: Query parameter input producers are not currently supported (parameter %s not found in path)." rn - None | ResourcePath accessPath -> let bc = bodyConsumers |> Array.tryFind (fun (reqId, s) -> reqId = a.producerId) @@ -1026,6 +1071,9 @@ let createInputOnlyProducerFromAnnotation (a:ProducerConsumerAnnotation) InputOnlyProducer.id = resource parameterKind = ParameterKind.Body } + + // The producer needs to be found both for the consumer parameter (reader) + // and for the producer parameter itself (writer) Some (c.id.ResourceName, inputOnlyProducer) @@ -1216,10 +1264,11 @@ let extractDependencies (requestData:(RequestId*RequestData)[]) rd.localAnnotations |> Seq.iter (fun a -> if a.producerParameter.IsSome then - let ip = createInputOnlyProducerFromAnnotation a pathConsumers queryConsumers bodyConsumers + let ip = createInputOnlyProducerFromAnnotation a pathConsumers queryConsumers bodyConsumers headerConsumers if ip.IsSome then let resourceName, producer = ip.Value producers.addInputOnlyProducer(resourceName, producer) + ) ) @@ -1229,7 +1278,7 @@ let extractDependencies (requestData:(RequestId*RequestData)[]) globalAnnotations |> Seq.iter (fun a -> if a.producerParameter.IsSome then - let ip = createInputOnlyProducerFromAnnotation a pathConsumers queryConsumers bodyConsumers + let ip = createInputOnlyProducerFromAnnotation a pathConsumers queryConsumers bodyConsumers headerConsumers if ip.IsSome then let resourceName, producer = ip.Value producers.addInputOnlyProducer(resourceName, producer) @@ -1485,12 +1534,12 @@ module DependencyLookup = // Mark the type of the dynamic object to be the type of the input parameter if available let primitiveType = match defaultPayload with - | FuzzingPayload.Fuzzable (primitiveType, _, _, _) -> primitiveType + | FuzzingPayload.Fuzzable fp -> fp.primitiveType | _ -> printfn "Warning: primitive type not available for %A [resource: %s]" requestId consumerResourceName responseProducer.id.PrimitiveType DynamicObject { primitiveType = primitiveType; variableName = variableName; isWriter = false } - | Some (InputParameter (inputParameterProducer, dictionaryPayload)) -> + | Some (InputParameter (inputParameterProducer, dictionaryPayload, isWriter)) -> let accessPath = inputParameterProducer.getInputParameterAccessPath() let variableName = DynamicObjectNaming.generateDynamicObjectVariableName @@ -1499,14 +1548,24 @@ module DependencyLookup = // Mark the type of the dynamic object to be the type of the input parameter if available let primitiveType = match defaultPayload with - | FuzzingPayload.Fuzzable (primitiveType, _, _, _) -> primitiveType + | FuzzingPayload.Fuzzable fp -> fp.primitiveType | _ -> printfn "Warning: primitive type not available for %A [resource: %s]" requestId consumerResourceName inputParameterProducer.id.PrimitiveType let dynamicObject = { primitiveType = primitiveType; variableName = variableName; isWriter = false } match dictionaryPayload with - | None -> DynamicObject dynamicObject + | None -> + if isWriter then + match defaultPayload with + | Fuzzable fp -> + Fuzzable { fp with dynamicObject = Some dynamicObject } + | Custom cp -> + Custom { cp with dynamicObject = Some dynamicObject } + | _ -> + failwith "Input producers are not supported for this payload type." + else + DynamicObject dynamicObject | Some dp -> Custom { payloadType = dp.payloadType primitiveType = dp.primitiveType @@ -1596,7 +1655,11 @@ module DependencyLookup = else let defaultPayload = match p.payload with - | None -> Fuzzable (PrimitiveType.String, "", None, None) + | None -> Fuzzable { primitiveType = PrimitiveType.String + defaultValue = "" + exampleValue = None + parameterName = None + dynamicObject = None } | Some p -> p let propertyAccessPath = { path = PropertyAccessPaths.getInnerAccessPath resourceAccessPath p @@ -1620,14 +1683,24 @@ module DependencyLookup = | Tree.LeafNode leafProperty -> leafProperty.payload, leafProperty.isRequired, leafProperty.isReadOnly | Tree.InternalNode (i, _) -> + let defaultFuzzablePayload = + { primitiveType = PrimitiveType.String + defaultValue = "" + exampleValue = None + parameterName = None + dynamicObject = None } let payload = match i.propertyType with | NestedType.Object -> - (Fuzzable (PrimitiveType.Object, "{}", None, None)) + Fuzzable {defaultFuzzablePayload with + primitiveType = PrimitiveType.Object + defaultValue = "{}" } | NestedType.Array -> - (Fuzzable (PrimitiveType.Object, "[]", None, None)) + Fuzzable {defaultFuzzablePayload with + primitiveType = PrimitiveType.Object + defaultValue = "[]" } | NestedType.Property -> - (Fuzzable (PrimitiveType.String, "", None, None)) + Fuzzable defaultFuzzablePayload payload, i.isRequired, i.isReadOnly let dependencyPayload = getConsumerPayload dependencies pathPayload requestId parameterName EmptyAccessPath defaultPayload @@ -1728,7 +1801,7 @@ let writeDependencies dependenciesFilePath dependencies (unresolvedOnly:bool) = rp.id.RequestId.endpoint, getMethod rp.id, getParameter rp.id - | Some (InputParameter (ip, dp)) -> // TODO: what to serialize? + | Some (InputParameter (ip, dp, _)) -> // TODO: what to serialize? ip.id.RequestId.endpoint, getMethod ip.id, getParameter ip.id diff --git a/src/compiler/Restler.Compiler/Grammar.fs b/src/compiler/Restler.Compiler/Grammar.fs index 962f993..6773ae0 100644 --- a/src/compiler/Restler.Compiler/Grammar.fs +++ b/src/compiler/Restler.Compiler/Grammar.fs @@ -166,6 +166,26 @@ type CustomPayload = dynamicObject: DynamicObject option } +type FuzzablePayload = + { + /// The primitive type of the payload, as declared in the specification + primitiveType : PrimitiveType + + /// The default value of the payload + defaultValue : string + + /// The example value specified in the spec, if any + exampleValue : string option + + /// The parameter name, if available. + parameterName : string option + + /// The associated dynamic object, whose value should be + /// assigned to the value generated from this payload. + /// For example, an input value from a request body property. + dynamicObject: DynamicObject option + } + /// The payload for a property specified in as a request parameter type FuzzingPayload = /// Example: (Int "1") @@ -173,7 +193,7 @@ type FuzzingPayload = /// (data type, default value, example value, parameter name) /// Example: (Int "1", "2") - | Fuzzable of PrimitiveType * string * string option * string option + | Fuzzable of FuzzablePayload /// 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 c93f316..4e0f495 100644 --- a/src/compiler/Restler.Compiler/SwaggerVisitors.fs +++ b/src/compiler/Restler.Compiler/SwaggerVisitors.fs @@ -119,7 +119,16 @@ module SchemaUtilities = let getFuzzableValueForObjectType (objectType:NJsonSchema.JsonObjectType) (format:string) (exampleValue: string option) (propertyName: string option) (trackParameters:bool) = - Fuzzable (getGrammarPrimitiveTypeWithDefaultValue objectType format exampleValue propertyName trackParameters) + let primitiveType, defaultValue, exampleValue, propertyName = + getGrammarPrimitiveTypeWithDefaultValue objectType format exampleValue propertyName trackParameters + Fuzzable + { + primitiveType = primitiveType + defaultValue = defaultValue + exampleValue = exampleValue + parameterName = propertyName + dynamicObject = None + } /// Get a boolean property from 'ExtensionData', if it exists. let getExtensionDataBooleanPropertyValue (extensionData:System.Collections.Generic.IDictionary) (extensionDataKeyName:string) = @@ -224,7 +233,14 @@ module SwaggerVisitors = match enumValues with | [] -> "null" | h::rest -> h - Fuzzable (PrimitiveType.Enum (propertyName, grammarPrimitiveType, enumValues, defaultValue), defaultFuzzableEnumValue, exv, None) + Fuzzable + { + primitiveType = PrimitiveType.Enum (propertyName, grammarPrimitiveType, enumValues, defaultValue) + defaultValue = defaultFuzzableEnumValue + exampleValue = exv + parameterName = None + dynamicObject = None + } | NJsonSchema.JsonObjectType.Object | NJsonSchema.JsonObjectType.None -> // Example of JsonObjectType.None: "content": {} without a type specified in Swagger. @@ -232,7 +248,14 @@ module SwaggerVisitors = getFuzzableValueForObjectType NJsonSchema.JsonObjectType.Object propertySchema.Format exampleValue (Some propertyName) trackParameters | NJsonSchema.JsonObjectType.File -> // Fuzz it as a string. - Fuzzable (PrimitiveType.String, "file object", None, if trackParameters then Some propertyName else None) + Fuzzable + { + primitiveType = PrimitiveType.String + defaultValue = "file object" + exampleValue = None + parameterName = if trackParameters then Some propertyName else None + dynamicObject = None + } | nst -> raise (UnsupportedType (sprintf "Unsupported type formatting: %A" nst)) { LeafProperty.name = propertyName; payload = payload ;isRequired = isRequired ; isReadOnly = isReadOnly } @@ -255,8 +278,8 @@ module SwaggerVisitors = | LeafNode leafProperty -> let payload = leafProperty.payload match payload with - | Fuzzable(a, b, c, _) -> - let payload = Fuzzable (a, b, c, Some paramName) + | Fuzzable fp -> + let payload = Fuzzable { fp with parameterName = Some paramName } LeafNode { leafProperty with LeafProperty.payload = payload } | x -> tree | InternalNode (_,_) -> tree @@ -383,15 +406,15 @@ module SwaggerVisitors = | Some v -> let examplePropertyPayload = match fuzzablePropertyPayload.payload with - | Fuzzable (primitiveType, defaultValue, _, propertyName) -> - let payloadValue = GenerateGrammarElements.formatJTokenProperty primitiveType v + | Fuzzable fp -> + let payloadValue = GenerateGrammarElements.formatJTokenProperty fp.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, propertyName) + Fuzzable { fp with exampleValue = Some payloadValue } else - Constant (primitiveType, payloadValue) + Constant (fp.primitiveType, payloadValue) | _ -> raise (invalidOp(sprintf "invalid payload %A, expected fuzzable" fuzzablePropertyPayload)) { fuzzablePropertyPayload with payload = examplePropertyPayload } @@ -507,7 +530,14 @@ module SwaggerVisitors = match s.Type with | JsonObjectType.Object -> // Do not insert a default fuzzable property for objects - Fuzzable (PrimitiveType.Object, "{ }", None, None) + Fuzzable + { + primitiveType = PrimitiveType.Object + defaultValue = "{ }" + exampleValue = None + parameterName = None + dynamicObject = None + } | _ -> getFuzzableValueForObjectType s.Item.Type s.Format None None trackParameters @@ -523,7 +553,15 @@ module SwaggerVisitors = else Some (getValueForObjectType schema.Item) | JsonObjectType.None -> - Some (Fuzzable (PrimitiveType.Object, "{ }", None, None)) + let fp = + { + primitiveType = PrimitiveType.Object + defaultValue = "{ }" + exampleValue = None + parameterName = None + dynamicObject = None + } + Some (Fuzzable fp) | _ -> Some (getValueForObjectType schema) @@ -733,7 +771,14 @@ module SwaggerVisitors = let leafPayload = let exampleValue = GenerateGrammarElements.formatJTokenProperty primitiveType v if generateFuzzablePayloadsForExamples then - FuzzingPayload.Fuzzable (primitiveType, defaultValue, Some exampleValue, None) + FuzzingPayload.Fuzzable + { + primitiveType = primitiveType + defaultValue = defaultValue + exampleValue = Some exampleValue + parameterName = None + dynamicObject = None + } else FuzzingPayload.Constant (primitiveType, exampleValue)