From 92e3621f9c7d2dcb44f1cc4d523af82d79dc4e78 Mon Sep 17 00:00:00 2001 From: Heng Lu <79895375+ms-henglu@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:27:01 +0800 Subject: [PATCH] Branch 240419 body dynamic schema (#96) * Revert "`azapi_*` - support `payload` (#95)" This reverts commit 861bed6ee2620da28a85334bc21a3dc4f65ad00a. * `azapi_*` - the `body` field supports dynamic schema * fix tests * fix crash --- CHANGELOG.md | 8 ++-- internal/langserver/handlers/hover/hover.go | 21 ++------ .../TestCompletion_codeSample/expect.json | 44 +++-------------- .../TestCompletion_jsonencode/expect.json | 8 ++-- .../TestCompletion_payload_actionBody/main.tf | 2 +- .../main.tf | 2 +- .../TestCompletion_payload_prop/main.tf | 2 +- .../main.tf | 2 +- .../TestCompletion_payload_value/main.tf | 2 +- .../testdata/TestHover_payload_prop/main.tf | 2 +- .../TestHover_payload_propInArray/main.tf | 2 +- .../handlers/tfschema/body_candidates.go | 23 +-------- .../handlers/tfschema/candidates.go | 18 ------- internal/langserver/handlers/tfschema/init.go | 48 ++----------------- .../langserver/handlers/validate/validate.go | 12 +++-- internal/parser/hcl_node.go | 3 ++ 16 files changed, 42 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9691b4585..9fd4963f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ ## v1.13.0 (unreleased) ENHANCEMENTS: - Support the new bicep types. -- `azapi_resource` resource: Support for the `payload` property. -- `azapi_update_resource` resource: Support for the `payload` property. -- `azapi_resource_action` resource: Support for the `payload` property. -- `azapi_resource_action` data source: Support for the `payload` property. +- `azapi_resource` resource: The `body` field supports dynamic schema. +- `azapi_update_resource` resource: The `body` field supports dynamic schema. +- `azapi_resource_action` resource: The `body` field supports dynamic schema. +- `azapi_resource_action` data source: The `body` field supports dynamic schema. d ## v1.12.0 ENHANCEMENTS: diff --git a/internal/langserver/handlers/hover/hover.go b/internal/langserver/handlers/hover/hover.go index 16831ced8..4ae67fdd3 100644 --- a/internal/langserver/handlers/hover/hover.go +++ b/internal/langserver/handlers/hover/hover.go @@ -47,23 +47,6 @@ func HoverAtPos(data []byte, filename string, pos hcl.Pos, logger *log.Logger) * } } } - case "payload": - if parser.ContainsPos(attribute.NameRange, pos) { - return Hover(property.Name, property.Modifier, property.Type, property.Description, attribute.NameRange) - } - - bodyDef := tfschema.BodyDefinitionFromBlock(block) - if bodyDef == nil { - return nil - } - - tokens, _ := hclsyntax.LexExpression(data[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], filename, attribute.Expr.Range().Start) - hclNode := parser.BuildHclNode(tokens) - if hclNode == nil { - break - } - - return hoverOnBody(hclNode, pos, bodyDef) case "body": if parser.ContainsPos(attribute.NameRange, pos) { return Hover(property.Name, property.Modifier, property.Type, property.Description, attribute.NameRange) @@ -75,6 +58,10 @@ func HoverAtPos(data []byte, filename string, pos hcl.Pos, logger *log.Logger) * } hclNode := parser.JsonEncodeExpressionToHclNode(data, attribute.Expr) + if hclNode == nil { + tokens, _ := hclsyntax.LexExpression(data[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], filename, attribute.Expr.Range().Start) + hclNode = parser.BuildHclNode(tokens) + } if hclNode == nil { break } diff --git a/internal/langserver/handlers/testdata/TestCompletion_codeSample/expect.json b/internal/langserver/handlers/testdata/TestCompletion_codeSample/expect.json index 1427d6612..636e6c365 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_codeSample/expect.json +++ b/internal/langserver/handlers/testdata/TestCompletion_codeSample/expect.json @@ -210,36 +210,6 @@ "command": "editor.action.triggerSuggest" } }, - { - "label": "payload", - "labelDetails": {}, - "kind": 10, - "detail": "payload (Optional)", - "documentation": { - "kind": "markdown", - "value": "Type: `dynamic` \nA dynamic attribute that contains the request body used to create and update azure resource.\n" - }, - "sortText": "0006", - "insertTextFormat": 2, - "insertTextMode": 2, - "textEdit": { - "range": { - "start": { - "line": 2, - "character": 2 - }, - "end": { - "line": 2, - "character": 2 - } - }, - "newText": "payload = $0" - }, - "command": { - "title": "Suggest", - "command": "editor.action.triggerSuggest" - } - }, { "label": "tags", "labelDetails": {}, @@ -249,7 +219,7 @@ "kind": "markdown", "value": "Type: `map` \nA mapping of tags which should be assigned to the azure resource.\n" }, - "sortText": "0007", + "sortText": "0006", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -279,7 +249,7 @@ "kind": "markdown", "value": "Type: `list` \nA list of path that needs to be exported from response body.\n" }, - "sortText": "0008", + "sortText": "0007", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -309,7 +279,7 @@ "kind": "markdown", "value": "Type: `bool` \nWhether enabled the validation on `type` and `body` with embedded schema. Defaults to `true`.\n" }, - "sortText": "0009", + "sortText": "0008", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -339,7 +309,7 @@ "kind": "markdown", "value": "Type: `list` \nA list of ARM resource IDs which are used to avoid create/modify/delete azapi resources at the same time.\n" }, - "sortText": "0010", + "sortText": "0009", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -369,7 +339,7 @@ "kind": "markdown", "value": "Type: `list` \nA list of properties that should be ignored when comparing the `body` with its current state..\n" }, - "sortText": "0011", + "sortText": "0010", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -399,7 +369,7 @@ "kind": "markdown", "value": "Type: `bool` \nWhether ignore incorrect casing returned in `body` to suppress plan-diff. Defaults to `false`.\n" }, - "sortText": "0012", + "sortText": "0011", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -429,7 +399,7 @@ "kind": "markdown", "value": "Type: `bool` \nWhether ignore not returned properties like credentials in `body` to suppress plan-diff. Defaults to `false`.\n" }, - "sortText": "0013", + "sortText": "0012", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { diff --git a/internal/langserver/handlers/testdata/TestCompletion_jsonencode/expect.json b/internal/langserver/handlers/testdata/TestCompletion_jsonencode/expect.json index dfc2f6d70..440789639 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_jsonencode/expect.json +++ b/internal/langserver/handlers/testdata/TestCompletion_jsonencode/expect.json @@ -5,14 +5,14 @@ "isIncomplete": false, "items": [ { - "label": "jsonencode({})", + "label": "{}", "labelDetails": {}, "kind": 12, "documentation": { "kind": "markdown", - "value": "`jsonencode` encodes a given value to a string using JSON syntax." + "value": "dynamic attribute allows any valid HCL object." }, - "sortText": "jsonencode", + "sortText": "{}", "insertTextFormat": 2, "insertTextMode": 2, "textEdit": { @@ -26,7 +26,7 @@ "character": 12 } }, - "newText": "jsonencode({\n\t$0\n})" + "newText": "{\n\t$0\n}" }, "command": { "title": "Suggest", diff --git a/internal/langserver/handlers/testdata/TestCompletion_payload_actionBody/main.tf b/internal/langserver/handlers/testdata/TestCompletion_payload_actionBody/main.tf index 0554493d4..1f650352b 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_payload_actionBody/main.tf +++ b/internal/langserver/handlers/testdata/TestCompletion_payload_actionBody/main.tf @@ -1,7 +1,7 @@ resource "azapi_resource_action" "test" { type = "Microsoft.AppPlatform/Spring@2022-05-01-preview" action = "regenerateTestKey" - payload = { + body = { } } \ No newline at end of file diff --git a/internal/langserver/handlers/testdata/TestCompletion_payload_discriminated/main.tf b/internal/langserver/handlers/testdata/TestCompletion_payload_discriminated/main.tf index e89c072dc..b51870469 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_payload_discriminated/main.tf +++ b/internal/langserver/handlers/testdata/TestCompletion_payload_discriminated/main.tf @@ -2,7 +2,7 @@ resource "azapi_resource" "dataflow" { type = "Microsoft.DataFactory/factories/dataflows@2018-06-01" name = "hengludf0509" parent_id = azurerm_data_factory.test.id - payload = { + body = { properties = { type = "Flowlet" typeProperties = { diff --git a/internal/langserver/handlers/testdata/TestCompletion_payload_prop/main.tf b/internal/langserver/handlers/testdata/TestCompletion_payload_prop/main.tf index 46c75dee4..81b570310 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_payload_prop/main.tf +++ b/internal/langserver/handlers/testdata/TestCompletion_payload_prop/main.tf @@ -2,7 +2,7 @@ resource "azapi_resource" "test" { name = "acctest1774" parent_id = azurerm_batch_account.test.id type = "Microsoft.DataFactory/factories@2018-06-01" - payload = { + body = { identity = { type = "SystemAssigned" } diff --git a/internal/langserver/handlers/testdata/TestCompletion_payload_propWrappedByQuote/main.tf b/internal/langserver/handlers/testdata/TestCompletion_payload_propWrappedByQuote/main.tf index 8428f9435..5002dee8b 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_payload_propWrappedByQuote/main.tf +++ b/internal/langserver/handlers/testdata/TestCompletion_payload_propWrappedByQuote/main.tf @@ -2,7 +2,7 @@ resource "azapi_resource" "test" { name = "acctest1774" parent_id = azurerm_batch_account.test.id type = "Microsoft.DataFactory/factories@2018-06-01" - payload = { + body = { "properties": { "encryption": { "identity": { diff --git a/internal/langserver/handlers/testdata/TestCompletion_payload_value/main.tf b/internal/langserver/handlers/testdata/TestCompletion_payload_value/main.tf index 3b09ca13a..043fcb1b6 100644 --- a/internal/langserver/handlers/testdata/TestCompletion_payload_value/main.tf +++ b/internal/langserver/handlers/testdata/TestCompletion_payload_value/main.tf @@ -2,7 +2,7 @@ resource "azapi_resource" "test" { name = "acctest1774" parent_id = azurerm_batch_account.test.id type = "Microsoft.DataFactory/factories@2018-06-01" - payload = { + body = { identity = { type = "" } diff --git a/internal/langserver/handlers/testdata/TestHover_payload_prop/main.tf b/internal/langserver/handlers/testdata/TestHover_payload_prop/main.tf index 46c75dee4..81b570310 100644 --- a/internal/langserver/handlers/testdata/TestHover_payload_prop/main.tf +++ b/internal/langserver/handlers/testdata/TestHover_payload_prop/main.tf @@ -2,7 +2,7 @@ resource "azapi_resource" "test" { name = "acctest1774" parent_id = azurerm_batch_account.test.id type = "Microsoft.DataFactory/factories@2018-06-01" - payload = { + body = { identity = { type = "SystemAssigned" } diff --git a/internal/langserver/handlers/testdata/TestHover_payload_propInArray/main.tf b/internal/langserver/handlers/testdata/TestHover_payload_propInArray/main.tf index d8277b836..01cb8a689 100644 --- a/internal/langserver/handlers/testdata/TestHover_payload_propInArray/main.tf +++ b/internal/langserver/handlers/testdata/TestHover_payload_propInArray/main.tf @@ -3,7 +3,7 @@ resource "azapi_resource" "route" { name = "henglu38" parent_id = azapi_resource.gateway.id - payload = { + body = { properties = { appResourceId = azapi_resource.app.id routes = [ diff --git a/internal/langserver/handlers/tfschema/body_candidates.go b/internal/langserver/handlers/tfschema/body_candidates.go index 1446f7ca6..a79034bf3 100644 --- a/internal/langserver/handlers/tfschema/body_candidates.go +++ b/internal/langserver/handlers/tfschema/body_candidates.go @@ -30,28 +30,9 @@ func bodyCandidates(data []byte, filename string, block *hclsyntax.Block, attrib hclNode := parser.JsonEncodeExpressionToHclNode(data, attribute.Expr) if hclNode == nil { - return nil - } - - return buildCandidates(hclNode, filename, pos, bodyDef) -} - -func payloadCandidates(data []byte, filename string, block *hclsyntax.Block, attribute *hclsyntax.Attribute, pos hcl.Pos, property *Property) []lsp.CompletionItem { - if attribute.Expr != nil { - if _, ok := attribute.Expr.(*hclsyntax.LiteralValueExpr); ok && parser.ToLiteral(attribute.Expr) == nil { - if property != nil { - return property.ValueCandidatesFunc(nil, editRangeFromExprRange(attribute.Expr, pos)) - } - } + tokens, _ := hclsyntax.LexExpression(data[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], filename, attribute.Expr.Range().Start) + hclNode = parser.BuildHclNode(tokens) } - - bodyDef := BodyDefinitionFromBlock(block) - if bodyDef == nil { - return nil - } - - tokens, _ := hclsyntax.LexExpression(data[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], filename, attribute.Expr.Range().Start) - hclNode := parser.BuildHclNode(tokens) if hclNode == nil { return nil } diff --git a/internal/langserver/handlers/tfschema/candidates.go b/internal/langserver/handlers/tfschema/candidates.go index e667c0135..ad49942e0 100644 --- a/internal/langserver/handlers/tfschema/candidates.go +++ b/internal/langserver/handlers/tfschema/candidates.go @@ -143,24 +143,6 @@ func boolCandidates(_ *string, r lsp.Range) []lsp.CompletionItem { return valueCandidates([]string{"true", "false"}, r, false) } -func bodyJsonencodeFuncCandidate() lsp.CompletionItem { - return lsp.CompletionItem{ - Label: `jsonencode({})`, - Kind: lsp.ValueCompletion, - Documentation: lsp.MarkupContent{ - Kind: "markdown", - Value: "`jsonencode` encodes a given value to a string using JSON syntax.", - }, - SortText: `jsonencode`, - InsertTextFormat: lsp.SnippetTextFormat, - InsertTextMode: lsp.AdjustIndentation, - TextEdit: &lsp.TextEdit{ - NewText: "jsonencode({\n\t$0\n})", - }, - Command: constTriggerSuggestCommand(), - } -} - func dynamicPlaceholderCandidate() lsp.CompletionItem { return lsp.CompletionItem{ Label: `{}`, diff --git a/internal/langserver/handlers/tfschema/init.go b/internal/langserver/handlers/tfschema/init.go index 5427d5265..e67625d89 100644 --- a/internal/langserver/handlers/tfschema/init.go +++ b/internal/langserver/handlers/tfschema/init.go @@ -75,18 +75,8 @@ func init() { Type: "string ", Description: "A JSON object that contains the request body used to create and update azure resource.", CompletionNewText: `body = $0`, - ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{bodyJsonencodeFuncCandidate()}), - GenericCandidatesFunc: bodyCandidates, - }, - - { - Name: "payload", - Modifier: "Optional", - Type: "dynamic", - Description: "A dynamic attribute that contains the request body used to create and update azure resource.", - CompletionNewText: `payload = $0`, ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{dynamicPlaceholderCandidate()}), - GenericCandidatesFunc: payloadCandidates, + GenericCandidatesFunc: bodyCandidates, }, { @@ -191,18 +181,8 @@ func init() { Type: "string ", Description: "A JSON object that contains the request body used to create and update azure resource.", CompletionNewText: `body = $0`, - ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{bodyJsonencodeFuncCandidate()}), - GenericCandidatesFunc: bodyCandidates, - }, - - { - Name: "payload", - Modifier: "Optional", - Type: "dynamic", - Description: "A dynamic attribute that contains the request body used to create and update azure resource.", - CompletionNewText: `payload = $0`, ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{dynamicPlaceholderCandidate()}), - GenericCandidatesFunc: payloadCandidates, + GenericCandidatesFunc: bodyCandidates, }, { @@ -329,18 +309,8 @@ func init() { Type: "string ", Description: "A JSON object that contains the request body.", CompletionNewText: `body = $0`, - ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{bodyJsonencodeFuncCandidate()}), - GenericCandidatesFunc: bodyCandidates, - }, - - { - Name: "payload", - Modifier: "Optional", - Type: "dynamic", - Description: "A dynamic attribute that contains the request body used to create and update azure resource.", - CompletionNewText: `payload = $0`, ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{dynamicPlaceholderCandidate()}), - GenericCandidatesFunc: payloadCandidates, + GenericCandidatesFunc: bodyCandidates, }, { @@ -396,18 +366,8 @@ func init() { Type: "string ", Description: "A JSON object that contains the request body.", CompletionNewText: `body = $0`, - ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{bodyJsonencodeFuncCandidate()}), - GenericCandidatesFunc: bodyCandidates, - }, - - { - Name: "payload", - Modifier: "Optional", - Type: "dynamic", - Description: "A dynamic attribute that contains the request body used to create and update azure resource.", - CompletionNewText: `payload = $0`, ValueCandidatesFunc: FixedValueCandidatesFunc([]lsp.CompletionItem{dynamicPlaceholderCandidate()}), - GenericCandidatesFunc: payloadCandidates, + GenericCandidatesFunc: bodyCandidates, }, { diff --git a/internal/langserver/handlers/validate/validate.go b/internal/langserver/handlers/validate/validate.go index 92327dd6c..569fbd6ed 100644 --- a/internal/langserver/handlers/validate/validate.go +++ b/internal/langserver/handlers/validate/validate.go @@ -70,11 +70,10 @@ func ValidateBlock(src []byte, block *hclsyntax.Block) hcl.Diagnostics { if bodyAttribute := parser.AttributeWithName(block, "body"); bodyAttribute != nil { attribute = bodyAttribute hclNode = parser.JsonEncodeExpressionToHclNode(src, attribute.Expr) - } - if payloadAttribute := parser.AttributeWithName(block, "payload"); payloadAttribute != nil { - attribute = payloadAttribute - tokens, _ := hclsyntax.LexExpression(src[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], "", attribute.Expr.Range().Start) - hclNode = parser.BuildHclNode(tokens) + if hclNode == nil { + tokens, _ := hclsyntax.LexExpression(src[attribute.Expr.Range().Start.Byte:attribute.Expr.Range().End.Byte], "", attribute.Expr.Range().Start) + hclNode = parser.BuildHclNode(tokens) + } } if attribute == nil || hclNode == nil { return nil @@ -83,6 +82,9 @@ func ValidateBlock(src []byte, block *hclsyntax.Block) hcl.Diagnostics { if dummy, ok := hclNode.Children["dummy"]; ok { dummy.KeyRange = attribute.NameRange if nameAttribute := parser.AttributeWithName(block, "name"); nameAttribute != nil { + if dummy.Children == nil { + dummy.Children = make(map[string]*parser.HclNode) + } dummy.Children["name"] = &parser.HclNode{ Value: parser.ToLiteral(nameAttribute.Expr), Key: "name", diff --git a/internal/parser/hcl_node.go b/internal/parser/hcl_node.go index 466499f70..f50bfc6d2 100644 --- a/internal/parser/hcl_node.go +++ b/internal/parser/hcl_node.go @@ -314,6 +314,9 @@ func BuildHclNode(tokens hclsyntax.Tokens) *HclNode { } } default: + if len(stack) == 0 { + break + } top := len(stack) - 1 if !stack[top].ExpectEqual { updateStateValue(&stack[top], token)