From 5d8b6b0200b9f13b3eb2ff1f540d61257298f79c Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Mon, 24 Jun 2019 14:27:18 +0200 Subject: [PATCH] IDEA-212654 strange behaviour when working with file named function.json GitOrigin-RevId: 69ff015b31cbe6d27c12f2f519f5ff07d2a7d6f4 --- .../impl/JsonSchemaVariantsTreeBuilder.java | 33 +- .../JsonSchemaHighlightingTest.java | 5 + .../highlighting/functionSchema.json | 501 ++++++++++++++++++ 3 files changed, 525 insertions(+), 14 deletions(-) create mode 100644 json/tests/testData/jsonSchema/highlighting/functionSchema.json diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaVariantsTreeBuilder.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaVariantsTreeBuilder.java index a74a1ed591be..820db9136a4e 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaVariantsTreeBuilder.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaVariantsTreeBuilder.java @@ -156,19 +156,32 @@ public class JsonSchemaVariantsTreeBuilder { } @Nullable - protected Operation createExpandOperation(@NotNull final JsonSchemaObject schema, + protected Operation createExpandOperation(@NotNull JsonSchemaObject schema, @NotNull JsonSchemaService service) { - if (conflictingSchema(schema)) { - final Operation operation = new AnyOfOperation(schema, service); - operation.myState = SchemaResolveState.conflict; - return operation; - } + Operation forConflict = getOperationForConflict(schema, service); + if (forConflict != null) return forConflict; if (schema.getAnyOf() != null) return new AnyOfOperation(schema, service); if (schema.getOneOf() != null) return new OneOfOperation(schema, service); if (schema.getAllOf() != null) return new AllOfOperation(schema, service); return null; } + @Nullable + private static Operation getOperationForConflict(@NotNull JsonSchemaObject schema, + @NotNull JsonSchemaService service) { + // in case of several incompatible operations, choose the most permissive one + List anyOf = schema.getAnyOf(); + List oneOf = schema.getOneOf(); + List allOf = schema.getAllOf(); + if (anyOf != null && (oneOf != null || allOf != null)) { + return new AnyOfOperation(schema, service) {{myState = SchemaResolveState.conflict;}}; + } + else if (oneOf != null && allOf != null) { + return new OneOfOperation(schema, service) {{myState = SchemaResolveState.conflict;}}; + } + return null; + } + protected static List mergeOneOf(Operation op) { return op.myOneOfGroup.stream().flatMap(List::stream).collect(Collectors.toList()); } @@ -349,14 +362,6 @@ public class JsonSchemaVariantsTreeBuilder { } } - private static boolean conflictingSchema(JsonSchemaObject schema) { - int cnt = 0; - if (schema.getAllOf() != null) ++cnt; - if (schema.getAnyOf() != null) ++cnt; - if (schema.getOneOf() != null) ++cnt; - return cnt > 1; - } - private static boolean interestingSchema(@NotNull JsonSchemaObject schema) { return schema.getAnyOf() != null || schema.getOneOf() != null || schema.getAllOf() != null || schema.getRef() != null || schema.getIfThenElse() != null; diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index 8858ce0b1a4c..c2224f7cd4f3 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -1172,4 +1172,9 @@ public class JsonSchemaHighlightingTest extends JsonSchemaHighlightingTestBase { " }\n" + "}", "{\"q\": \"aA\", \"r\": \"Bb\", \"s\": \"aB\"}"); } + + public void testFunctionSchema() throws Exception { + @Language("JSON") String schemaText = FileUtil.loadFile(new File(getTestDataPath() + "/functionSchema.json")); + doTest(schemaText, "{\"bindings\": [\"queueTrigger\"]}"); + } } diff --git a/json/tests/testData/jsonSchema/highlighting/functionSchema.json b/json/tests/testData/jsonSchema/highlighting/functionSchema.json new file mode 100644 index 000000000000..04af313e1700 --- /dev/null +++ b/json/tests/testData/jsonSchema/highlighting/functionSchema.json @@ -0,0 +1,501 @@ +{ + "title": "JSON schema for Azure Functions function.json files", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "disabled": { + "type": "boolean", + "description": "If set to true, marks the function as disabled (it cannot be triggered)." + }, + "excluded": { + "type": "boolean", + "description": "If set to true, the function will not be loaded, compiled, or triggered." + }, + "scriptFile": { + "type": "string", + "description": "Optional path to function script file." + }, + "entryPoint": { + "type": "string", + "description": "Optional named entry point." + }, + "configurationSource": { + "enum": [ "attributes", "config" ], + "description": "For C# precompiled functions only. If set to 'attributes', use WebJobs attributes to specify bindings. Otherwise, use the 'bindings' property of this function.json." + }, + "bindings": { + "type": "array", + "description": "A list of function bindings.", + "items": { + "oneOf": [ + { "$ref": "#/definitions/dynamicBinding" }, + { + "oneOf": [ + { "$ref": "#/definitions/serviceBusBinding" }, + { "$ref": "#/definitions/blobBinding" }, + { "$ref": "#/definitions/manualTriggerBinding" }, + { "$ref": "#/definitions/eventHubBinding" }, + { "$ref": "#/definitions/timerTriggerBinding" }, + { "$ref": "#/definitions/queueBinding" }, + { "$ref": "#/definitions/httpBinding" }, + { "$ref": "#/definitions/mobileBinding" }, + { "$ref": "#/definitions/documentDBBinding" }, + { "$ref": "#/definitions/tableBinding" }, + { "$ref": "#/definitions/notificationHubBinding" }, + { "$ref": "#/definitions/twilioSmsBinding" }, + { "$ref": "#/definitions/sendGridBinding" } + ] + } + ], + "allOf": [ + { "$ref": "#/definitions/bindingBase" } + ] + } + } + }, + "definitions": { + "bindingBase": { + "properties": { + "name": { "type": "string" }, + "type": { "type": "string" }, + "direction": { + "type": "string", + "enum": [ "in", "out", "inout" ] + }, + "dataType": { + "enum": [ "string", "binary", "stream" ], + "description": "The data type hint for the binding parameter (string, binary, or stream)." + } + }, + "required": [ "name", "type", "direction" ] + }, + "dynamicBinding": { + "properties": { + "type": { + "not": { + "enum": [ + "serviceBusTrigger", + "serviceBus", + "blobTrigger", + "blob", + "manualTrigger", + "eventHubTrigger", + "eventHub", + "timerTrigger", + "queueTrigger", + "queue", + "httpTrigger", + "http", + "mobileTable", + "documentDB", + "table", + "notificationHub", + "twilioSms", + "sendGrid" + ] + } + }, + "direction": { "enum": [ "in", "out", "inout" ] } + } + }, + "serviceBusBinding": { + "properties": { + "queueName": { + "type": "string", + "description": "The service bus queue to monitor (if using a queue)" + }, + "topicName": { + "type": "string", + "description": "The service bus topic to monitor (if using a queue)" + }, + "subscriptionName": { + "type": "string", + "description": "The topic subscription name" + }, + "connection": { + "type": "string", + "description": "An app setting (or environment variable) with the service bus connection string to be used by this binding." + }, + "accessRights": { + "type": "string", + "description": "The permission level of the service bus connection string used by this binding.", + "enum": [ + "manage", + "listen" + ] + } + }, + "oneOf": [ + { + "properties": { + "type": { "enum": [ "serviceBusTrigger" ] }, + "direction": { "enum": [ "in" ] } + } + }, + { + "properties": { + "type": { "enum": [ "serviceBus" ] }, + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "blobBinding": { + "properties": { + "path": { + "type": "string", + "description": "The path to the blob container" + }, + "connection": { + "type": "string", + "description": "An app setting (or environment variable) with the storage connection string to be used by this binding." + } + }, + "oneOf": [ + { + "properties": { + "type": { "enum": [ "blobTrigger" ] }, + "direction": { "enum": [ "in" ] } + } + }, + { + "properties": { + "type": { "enum": [ "blob" ] } + } + } + ] + }, + "manualTriggerBinding": { + "properties": { + "type": { "enum": [ "manualTrigger" ] }, + "direction": { "enum": [ "in" ] } + } + }, + "eventHubBinding": { + "properties": { + "path": { + "type": "string", + "description": "The event hub path." + }, + "connection": { + "type": "string", + "description": "The event hub connection string setting." + } + }, + "oneOf": [ + { + "properties": { + "type": { "enum": [ "eventHubTrigger" ] }, + "direction": { "enum": [ "in" ] }, + "consumerGroup": { + "type": "string", + "description": "The event hub consumer group." + }, + "cardinality": { + "enum": [ "one", "many" ], + "description": "The cardinality hint for the input binding parameter (single message or array of messages)." + } + } + }, + { + "properties": { + "type": { "enum": [ "eventHub" ] }, + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "timerTriggerBinding": { + "properties": { + "type": { "enum": [ "timerTrigger" ] }, + "direction": { "enum": [ "in" ] }, + "schedule": { + "type": "string", + "description": "A CRON expression representing the timer schedule.", + "pattern": "^(\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?)(,(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?)(,((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?)(,(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?)(,(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?))*)(\\/\\d+)?$" + }, + "runOnStartup": { + "type": "boolean", + "description": "When true, your timer function will be invoked immediately after a runtime restart and on-schedule thereafter." + }, + "useMonitor": { + "type": "boolean", + "description": "When true, schedule will be persisted to aid in maintaining the correct schedule even through restarts. Defaults to true for schedules with interval >= 1 minute." + } + } + }, + "queueBinding": { + "properties": { + "queueName": { + "type": "string", + "description": "The queue name." + }, + "connection": { + "type": "string", + "description": "An app setting (or environment variable) with the storage connection string to be used by this binding." + } + }, + "oneOf": [ + { + "properties": { + "type": { "enum": [ "queueTrigger" ] }, + "direction": { "enum": [ "in" ] } + } + }, + { + "properties": { + "type": { "enum": [ "queue" ] }, + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "httpTrigger": { + + }, + "httpBinding": { + "oneOf": [ + { + "properties": { + "type": { "enum": [ "httpTrigger" ] }, + "direction": { "enum": [ "in" ] }, + "route": { + "type": "string", + "description": "The function HTTP route template." + }, + "webHookType": { + "type": "string", + "description": "The type of WebHook handled by the trigger (if handling a pre-defined WebHook)." + }, + "authLevel": { + "type": "string", + "default": "function", + "enum": [ "anonymous", "function", "admin" ], + "description": "The function HTTP authorization level." + }, + "methods": { + "type": "array", + "items": [ + { + "type": "string", + "enum": [ + "get", + "post", + "delete", + "head", + "patch", + "put", + "options", + "trace" + ] + } + ] + } + } + }, + { + "properties": { + "type": { "enum": [ "http" ] }, + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "mobileBinding": { + "properties": { + "type": { "enum": [ "mobileTable" ] }, + "tableName": { + "type": "string", + "description": "This is the name of the table within your Mobile App to which data will be written." + }, + "connection": { + "type": "string", + "description": "This is the app setting name that specifies the URL of your Mobile App." + }, + "apiKey": { + "type": "string", + "description": "This is app setting name that specifies the API Key for your Mobile App." + } + }, + "oneOf": [ + { + "properties": { + "direction": { "enum": [ "in" ] }, + "id": { + "type": "string", + "description": "This is the id for the record to retrieve." + } + } + }, + { + "properties": { + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "documentDBBinding": { + "properties": { + "type": { "enum": [ "documentDB" ] }, + "connection": { + "type": "string", + "description": "This is the connection string for your DocumentDB account." + }, + "databaseName": { + "type": "string", + "description": "This is the name of the database within your DocumentDB account to which data will be written." + }, + "collectionName": { + "type": "string", + "description": "This is the name of the collection within your database to which data will be written." + } + }, + "oneOf": [ + { + "properties": { + "direction": { "enum": [ "in" ] }, + "id": { + "type": "string", + "description": "This is the id for the record to retrieve." + }, + "sqlQuery": { + "type": "string", + "description": "This is the query to run against the collection." + } + } + }, + { + "properties": { + "direction": { "enum": [ "out" ] }, + "createIfNotExists": { + "type": "boolean", + "description": "When true, your database and collection will be created automatically." + } + } + } + ] + }, + "tableBinding": { + "properties": { + "type": { "enum": [ "table" ] }, + "tableName": { + "type": "string", + "description": "The name of the storage table." + }, + "partitionKey": { + "type": "string", + "description": "The partition key." + }, + "rowKey": { + "type": "string", + "description": "The table row key." + }, + "connection": { + "type": "string", + "description": "An app setting (or environment variable) with the storage connection string to be used by this binding." + } + }, + "oneOf": [ + { + "properties": { + "direction": { "enum": [ "in" ] }, + "take": { + "type": "string", + "description": "The number or records to retrieve." + }, + "filter": { + "type": "string", + "description": "A filter expression to be applied when retrieving rows." + } + } + }, + { + "properties": { + "direction": { "enum": [ "out" ] } + } + } + ] + }, + "notificationHubBinding": { + "properties": { + "type": { "enum": [ "notificationHub" ] }, + "direction": { "enum": [ "out" ] }, + "tagExpression": { + "type": "string", + "description": "The tag to send the notification to." + }, + "hubName": { + "type": "string", + "description": "The name of the notification hub." + }, + "connection": { + "type": "string", + "description": "The name of the app setting which contains the notification hub connection string." + }, + "platform": { + "type": "string", + "description": "The notification platform to target. Sends template notification if platform is omitted.", + "enum": [ + "apns", + "adm", + "gcm", + "wns", + "mpns" + ] + } + } + }, + "twilioSmsBinding": { + "properties": { + "type": { "enum": [ "twilioSms" ] }, + "direction": { "enum": [ "out" ] }, + "accountSid": { + "type": "string", + "description": "The name of the app setting which contains your Twilio Account Sid." + }, + "authToken": { + "type": "string", + "description": "The name of the app setting which contains your Twilio authentication token." + }, + "to": { + "type": "string", + "description": "The phone number the SMS text is sent to." + }, + "from": { + "type": "string", + "description": "The phone number the SMS text is sent from." + }, + "body": { + "type": "string", + "description": "Optional body of SMS text message." + } + } + }, + "sendGridBinding": { + "properties": { + "type": { "enum": [ "sendGrid" ] }, + "direction": { "enum": [ "out" ] }, + "apiKey": { + "type": "string", + "description": "The name of the app setting which contains your SendGrid api key." + }, + "to": { + "type": "string", + "description": "The email address to send to." + }, + "from": { + "type": "string", + "description": "The email address to send from." + }, + "subject": { + "type": "string", + "description": "The subject of the email." + }, + "text": { + "type": "string", + "description": "The text of the email." + } + } + } + } +}