Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ When working in the `tools/cli` directory:
- `make e2e-test` - Run end-to-end tests
- `make build` - Build the CLI binary
- `make gen-docs` - Generate CLI documentation
- `go generate ./...` - Regenerate generated Go sources. Run this after editing
`tools/spectral/ipa/rulesets/functions/utils/legacyLongRunningOperations.json`; a unit test
fails if the generated copy is out of date, so a forgotten run cannot pass CI
- `go generate ./...` - Regenerate the Go copy of the legacy long-running operation list. Run
this after editing `tools/spectral/ipa/rulesets/functions/utils/legacyLroOperationIds.js`; a
unit test fails if the generated copy is out of date, so a forgotten run cannot pass CI

**Note: Please ensure the usage of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) in PR titles**
76 changes: 53 additions & 23 deletions tools/cli/internal/cli/merge/gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@
package main

import (
"encoding/json"
"fmt"
"go/format"
"log"
"os"
"regexp"
"sort"
"strings"
)

const (
sourcePath = "../../../../spectral/ipa/rulesets/functions/utils/legacyLongRunningOperations.json"
sourcePath = "../../../../spectral/ipa/rulesets/functions/utils/legacyLroOperationIds.js"
outputPath = "legacy_long_running_operations.go"

arrayStart = "export const legacyLroOperationIds = ["
arrayEnd = "];"
)

type legacyLongRunningOperations struct {
OperationIDs []string `json:"operationIds"`
}
var operationIDLine = regexp.MustCompile(`^ {2}'([a-zA-Z0-9]+)',$`)

func main() {
if err := run(); err != nil {
Expand All @@ -47,28 +48,16 @@ func run() error {
return fmt.Errorf("reading %s: %w", sourcePath, err)
}

var list legacyLongRunningOperations
if err = json.Unmarshal(data, &list); err != nil {
return fmt.Errorf("parsing %s: %w", sourcePath, err)
}

if len(list.OperationIDs) == 0 {
return fmt.Errorf("%s defines no operationIds", sourcePath)
}

seen := map[string]struct{}{}
for _, operationID := range list.OperationIDs {
if _, duplicate := seen[operationID]; duplicate {
return fmt.Errorf("%s defines %q more than once", sourcePath, operationID)
}
seen[operationID] = struct{}{}
operationIDs, err := extractOperationIDs(string(data))
if err != nil {
return err
}

sort.Strings(list.OperationIDs)
sort.Strings(operationIDs)

var builder strings.Builder
builder.WriteString("// Code generated by go generate; DO NOT EDIT.\n")
builder.WriteString("// Source: tools/spectral/ipa/rulesets/functions/utils/legacyLongRunningOperations.json\n")
builder.WriteString("// Source: tools/spectral/ipa/rulesets/functions/utils/legacyLroOperationIds.js\n")
builder.WriteString("//\n")
builder.WriteString("// Generated by this command:\n")
builder.WriteString("//\n")
Expand All @@ -77,7 +66,7 @@ func run() error {
builder.WriteString("// legacyLongRunningOperationIDs is the set of operations returning HTTP 202 that\n")
builder.WriteString("// predate IPA-132 and do not follow the long-running operation contract.\n")
builder.WriteString("var legacyLongRunningOperationIDs = map[string]struct{}{\n")
for _, operationID := range list.OperationIDs {
for _, operationID := range operationIDs {
fmt.Fprintf(&builder, "%q: {},\n", operationID)
}
builder.WriteString("}\n")
Expand All @@ -94,3 +83,44 @@ func run() error {

return nil
}

// extractOperationIDs reads the operationIds from the JavaScript module, accepting only the
// prettier-enforced format of one single-quoted operationId per line, so that any unexpected
// content fails the generation instead of being silently dropped.
func extractOperationIDs(source string) ([]string, error) {
lines := strings.Split(source, "\n")

start := -1
for i, line := range lines {
if line == arrayStart {
start = i + 1
break
}
}
if start == -1 {
return nil, fmt.Errorf("%s does not declare %q", sourcePath, arrayStart)
}

seen := map[string]struct{}{}
operationIDs := make([]string, 0, len(lines))
for _, line := range lines[start:] {
if line == arrayEnd {
if len(operationIDs) == 0 {
return nil, fmt.Errorf("%s declares no operationIds", sourcePath)
}
return operationIDs, nil
}

match := operationIDLine.FindStringSubmatch(line)
if match == nil {
return nil, fmt.Errorf("unexpected line %q in %s", line, sourcePath)
}
if _, duplicate := seen[match[1]]; duplicate {
return nil, fmt.Errorf("%s declares %q more than once", sourcePath, match[1])
}
seen[match[1]] = struct{}{}
operationIDs = append(operationIDs, match[1])
}

return nil, fmt.Errorf("%s does not terminate the operationIds array with %q", sourcePath, arrayEnd)
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,28 @@
package merge

import (
"encoding/json"
"os"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestLegacyLongRunningOperationIDs_MatchSharedList pins the generated Go map to the shared
// list the IPA-132 validation rules consume. On failure, run go generate ./... in tools/cli.
// JavaScript module the IPA-132 validation rules import. On failure, run go generate ./... in
// tools/cli.
func TestLegacyLongRunningOperationIDs_MatchSharedList(t *testing.T) {
data, err := os.ReadFile("../../../../spectral/ipa/rulesets/functions/utils/legacyLongRunningOperations.json")
data, err := os.ReadFile("../../../../spectral/ipa/rulesets/functions/utils/legacyLroOperationIds.js")
require.NoError(t, err)

var list struct {
OperationIDs []string `json:"operationIds"`
}
require.NoError(t, json.Unmarshal(data, &list))
matches := regexp.MustCompile(`'([a-zA-Z0-9]+)',`).FindAllStringSubmatch(string(data), -1)
require.NotEmpty(t, matches, "no operationIds found in the shared list")

assert.Len(t, legacyLongRunningOperationIDs, len(list.OperationIDs),
assert.Len(t, legacyLongRunningOperationIDs, len(matches),
"the generated map and the shared list differ in size; run go generate ./... in tools/cli")
for _, operationID := range list.OperationIDs {
assert.Contains(t, legacyLongRunningOperationIDs, operationID,
for _, match := range matches {
assert.Contains(t, legacyLongRunningOperationIDs, match[1],
"missing from the generated map; run go generate ./... in tools/cli")
}
}
Loading
Loading