Compare commits

...

12 Commits

Author SHA1 Message Date
Gara Dorta
98d74de18b
Merge 91bdd60c7a40c3b50ed0acf5fc3c5a8042aa3ee5 into db63fd15e0bc2e2de4cff0b1969b12c23508a8d7 2026-05-15 19:32:01 +02:00
Gara Dorta
91bdd60c7a Additional refactor 2026-05-15 19:31:52 +02:00
Gara Dorta
a7c91737b8 Refactor 2026-05-15 19:28:57 +02:00
Gara Dorta
af48a4304b Regenerate docs 2026-05-15 18:55:26 +02:00
Gara Dorta
9155e505af Remove check for running swag init first 2026-05-15 18:52:32 +02:00
Gara Dorta
d0ec5b1b28 Better variable naming 2026-05-15 18:51:22 +02:00
Gara Dorta
fa0f67fa69 Remove uneeded if statement 2026-05-15 18:34:50 +02:00
Gara Dorta
7aa70683aa Simplify the add_v1_receive_schemas.go my marshalling the whole document 2026-05-15 18:29:20 +02:00
Bernhard B.
db63fd15e0
Merge pull request #848 from arnehuang/add-permissions-block
Add empty permissions block at workflow level
2026-05-10 23:03:19 +02:00
Bernhard B.
650367e88a
Merge pull request #847 from arnehuang/pin-actions-checkout-sha
Pin actions/checkout to a commit SHA
2026-05-10 23:02:13 +02:00
Arne Huang
69457e8f81 Add empty permissions block at workflow level
Caps GITHUB_TOKEN's blast radius. None of these workflows need any
GitHub API write scope — they only push to Docker Hub — so the safest
default is permissions: {}, matching the posture used by AsamK/signal-cli.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:00:28 -07:00
Arne Huang
2e8171d84c Pin actions/checkout to a commit SHA
Follow-up to #838: actions/checkout was the only third-party action
left on a mutable ref (@master). Pin it to v6.0.2's commit SHA, matching
the pattern used for docker/setup-qemu-action, docker/setup-buildx-action,
and docker/login-action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:56:36 -07:00
6 changed files with 6388 additions and 6610 deletions

View File

@ -8,6 +8,8 @@ on:
branches: branches:
- '**' #every branch - '**' #every branch
permissions: {}
jobs: jobs:
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -25,7 +27,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub

View File

@ -7,6 +7,8 @@ on:
description: 'Version' description: 'Version'
required: true required: true
permissions: {}
jobs: jobs:
setup: setup:
@ -24,7 +26,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub

View File

@ -7,6 +7,8 @@ on:
description: 'Version' description: 'Version'
required: true required: true
permissions: {}
jobs: jobs:
setup: setup:
@ -24,7 +26,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub

View File

@ -8,7 +8,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strconv"
"strings" "strings"
_ "github.com/bbernhard/signal-cli-rest-api/docs" _ "github.com/bbernhard/signal-cli-rest-api/docs"
@ -19,9 +18,10 @@ const (
jsonDocsPath = "swagger.json" jsonDocsPath = "swagger.json"
openMarker = "const docTemplate = `" openMarker = "const docTemplate = `"
closeMarker = "`\n\n// SwaggerInfo" closeMarker = "`\n\n// SwaggerInfo"
definitionsKey = `"definitions": {` schemesTemplateValue = "{{ marshal .Schemes }}"
schemesPlaceholderToken = "__SWAG_SCHEMES_PLACEHOLDER__"
receivePrefix = "receive." receivePrefix = "receive."
receivePathKey = `"/v1/receive/{number}":` receivePathKey = "/v1/receive/{number}"
receiveWrapper = "data.Message" receiveWrapper = "data.Message"
) )
@ -46,22 +46,15 @@ func run(receiveDir string) error {
return err return err
} }
if err := updateReceiveSchemaRefs(definitions, titleByFile); err != nil { updateReceiveSchemaRefs(definitions, titleByFile)
return err
}
addEnvelopeWrapperDefinition(definitions) addEnvelopeWrapperDefinition(definitions)
managedDefinitions, err := renderManagedDefinitions(definitions) if err := updateDocsGo(definitions); err != nil {
if err != nil {
return err return err
} }
if err := updateDocsGo(managedDefinitions); err != nil { if err := updateSwaggerJSON(definitions); err != nil {
return err
}
if err := updateSwaggerJSON(managedDefinitions); err != nil {
return err return err
} }
@ -70,23 +63,24 @@ func run(receiveDir string) error {
return nil return nil
} }
func updateDocsGo(managedDefinitions string) error { func updateDocsGo(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(goDocsPath) content, err := os.ReadFile(goDocsPath)
if err != nil { if err != nil {
return fmt.Errorf("read %s: %w", goDocsPath, err) return fmt.Errorf("read %s: %w", goDocsPath, err)
} }
template, err := extractDocTemplate(string(content)) template, templateStart, templateEnd, err := extractDocTemplate(string(content))
if err != nil { if err != nil {
return err return err
} }
updatedTemplate, err := applyReceiveSchemaUpdates(template, managedDefinitions) updatedTemplate, err := updateJSONDocument(toValidJson(template), receiveDefinitions)
if err != nil { if err != nil {
return err return err
} }
updatedTemplate = encodeForGoRawString(updatedTemplate)
updated := strings.Replace(string(content), template, updatedTemplate, 1) updated := string(content[:templateStart]) + updatedTemplate + string(content[templateEnd:])
if err := os.WriteFile(goDocsPath, []byte(updated), 0644); err != nil { if err := os.WriteFile(goDocsPath, []byte(updated), 0644); err != nil {
return fmt.Errorf("write %s: %w", goDocsPath, err) return fmt.Errorf("write %s: %w", goDocsPath, err)
} }
@ -94,13 +88,13 @@ func updateDocsGo(managedDefinitions string) error {
return nil return nil
} }
func updateSwaggerJSON(managedDefinitions string) error { func updateSwaggerJSON(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(jsonDocsPath) content, err := os.ReadFile(jsonDocsPath)
if err != nil { if err != nil {
return fmt.Errorf("read %s: %w", jsonDocsPath, err) return fmt.Errorf("read %s: %w", jsonDocsPath, err)
} }
updated, err := applyReceiveSchemaUpdates(string(content), managedDefinitions) updated, err := updateJSONDocument(string(content), receiveDefinitions)
if err != nil { if err != nil {
return err return err
} }
@ -112,74 +106,50 @@ func updateSwaggerJSON(managedDefinitions string) error {
return nil return nil
} }
func applyReceiveSchemaUpdates(content string, managedDefinitions string) (string, error) { func extractDocTemplate(content string) (string, int, int, error) {
updated, err := appendDefinitionsEntries(content, managedDefinitions)
if err != nil {
return "", err
}
updated, err = replaceReceiveResponseSchema(updated)
if err != nil {
return "", err
}
return updated, nil
}
func extractDocTemplate(content string) (string, error) {
start := strings.Index(content, openMarker) start := strings.Index(content, openMarker)
if start == -1 { if start == -1 {
return "", fmt.Errorf("could not find docTemplate start in %s", goDocsPath) return "", -1, -1, fmt.Errorf("could not find docTemplate start in %s", goDocsPath)
} }
start += len(openMarker) start += len(openMarker)
endOffset := strings.Index(content[start:], closeMarker) endOffset := strings.Index(content[start:], closeMarker)
if endOffset == -1 { if endOffset == -1 {
return "", fmt.Errorf("could not find docTemplate end in %s", goDocsPath) return "", -1, -1, fmt.Errorf("could not find docTemplate end in %s", goDocsPath)
} }
return content[start : start+endOffset], nil end := start + endOffset
return content[start:end], start, end, nil
} }
func definitionsBounds(template string) (int, int, error) { func toValidJson(content string) string {
definitionsIndex := strings.Index(template, definitionsKey) content = strings.ReplaceAll(content, "` + \"`\" + `", "`")
if definitionsIndex == -1 { content = strings.Replace(content, schemesTemplateValue, `"`+schemesPlaceholderToken+`"`, 1)
return -1, -1, fmt.Errorf("could not find definitions block in docTemplate") return content
}
braceIndex := definitionsIndex + strings.Index(definitionsKey, "{")
closingBraceIndex, err := findMatchingBrace(template, braceIndex)
if err != nil {
return -1, -1, err
}
return braceIndex, closingBraceIndex, nil
} }
func appendDefinitionsEntries(template string, entries string) (string, error) { func encodeForGoRawString(content string) string {
braceIndex, closingBraceIndex, err := definitionsBounds(template) content = strings.ReplaceAll(content, "`", "` + \"`\" + `")
if err != nil { content = strings.Replace(content, `"`+schemesPlaceholderToken+`"`, schemesTemplateValue, 1)
return content
}
func updateJSONDocument(content string, receiveDefinitions map[string]interface{}) (string, error) {
var document map[string]interface{}
if err := json.Unmarshal([]byte(content), &document); err != nil {
return "", fmt.Errorf("parse document: %w", err)
}
if err := applyReceiveSchemaUpdates(document, receiveDefinitions); err != nil {
return "", err return "", err
} }
definitionsBlock := template[braceIndex : closingBraceIndex+1] raw, err := json.MarshalIndent(document, "", " ")
if strings.Contains(definitionsBlock, `"`+receiveWrapper+`"`) || strings.Contains(definitionsBlock, `"`+receivePrefix) { if err != nil {
return "", fmt.Errorf("definitions already contain receive entries; run swag init first") return "", fmt.Errorf("marshal document: %w", err)
} }
inner := strings.TrimSpace(template[braceIndex+1 : closingBraceIndex]) return string(raw), nil
if inner == "" {
return template[:braceIndex+1] + "\n" + entries + "\n" + template[closingBraceIndex:], nil
}
closingLine := "\n }"
insertIndex := strings.LastIndex(template[:closingBraceIndex+1], closingLine)
if insertIndex == -1 {
return "", fmt.Errorf("could not determine definitions closing line")
}
return template[:insertIndex] + ",\n" + entries + template[insertIndex:], nil
} }
func addReceiveSchemas(definitions map[string]interface{}, receiveDir string) (map[string]string, error) { func addReceiveSchemas(definitions map[string]interface{}, receiveDir string) (map[string]string, error) {
@ -247,15 +217,13 @@ func removeSchemaKeysRecursive(value interface{}, parentKey string) interface{}
} }
} }
func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) error { func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) {
for key, value := range definitions { for key, value := range definitions {
if !strings.HasPrefix(key, receivePrefix) { if !strings.HasPrefix(key, receivePrefix) {
continue continue
} }
definitions[key] = rewriteSchemaRefs(value, titleByFile) definitions[key] = rewriteSchemaRefs(value, titleByFile)
} }
return nil
} }
func rewriteSchemaRefs(value interface{}, titleByFile map[string]string) interface{} { func rewriteSchemaRefs(value interface{}, titleByFile map[string]string) interface{} {
@ -300,110 +268,60 @@ func addEnvelopeWrapperDefinition(definitions map[string]interface{}) {
} }
} }
func renderManagedDefinitions(definitions map[string]interface{}) (string, error) { func applyReceiveSchemaUpdates(document map[string]interface{}, receiveDefinitions map[string]interface{}) error {
keys := make([]string, 0, len(definitions)) definitions, err := getObject(document, "definitions")
if err != nil {
return err
}
for key := range definitions { for key := range definitions {
keys = append(keys, key) if strings.HasPrefix(key, receivePrefix) || key == receiveWrapper {
delete(definitions, key)
}
} }
sort.Strings(keys)
parts := make([]string, 0, len(keys)) for key, value := range receiveDefinitions {
for _, key := range keys { definitions[key] = value
raw, err := json.MarshalIndent(definitions[key], "", " ") }
paths, err := getObject(document, "paths")
if err != nil { if err != nil {
return "", fmt.Errorf("marshal definition %s: %w", key, err) return err
} }
receivePath, err := getObject(paths, receivePathKey)
lines := strings.Split(string(raw), "\n")
for idx := range lines {
lines[idx] = " " + lines[idx]
}
entry := " " + strconv.Quote(key) + ": " + strings.TrimPrefix(lines[0], " ")
if len(lines) > 1 {
entry += "\n" + strings.Join(lines[1:], "\n")
}
parts = append(parts, entry)
}
return strings.Join(parts, ",\n"), nil
}
func replaceReceiveResponseSchema(template string) (string, error) {
pathIndex := strings.Index(template, receivePathKey)
if pathIndex == -1 {
return "", fmt.Errorf("could not find receive path block; run swag init first")
}
braceOffset := strings.Index(template[pathIndex+len(receivePathKey):], "{")
if braceOffset == -1 {
return "", fmt.Errorf("could not find opening brace for receive path block")
}
pathOpenBrace := pathIndex + len(receivePathKey) + braceOffset
pathCloseBrace, err := findMatchingBrace(template, pathOpenBrace)
if err != nil { if err != nil {
return "", err return err
}
receiveGet, err := getObject(receivePath, "get")
if err != nil {
return err
}
responses, err := getObject(receiveGet, "responses")
if err != nil {
return err
}
response200, err := getObject(responses, "200")
if err != nil {
return err
} }
pathBlock := template[pathOpenBrace : pathCloseBrace+1] response200["schema"] = map[string]interface{}{
"$ref": "#/definitions/" + receiveWrapper,
oldSchema := `"schema": {
"type": "array",
"items": {
"type": "string"
}
}`
newSchema := `"schema": {
"$ref": "#/definitions/data.Message"
}`
updatedPathBlock := strings.Replace(pathBlock, oldSchema, newSchema, 1)
if updatedPathBlock == pathBlock {
return "", fmt.Errorf("could not replace /v1/receive schema; ensure generated docs are freshly generated by swag")
} }
return template[:pathOpenBrace] + updatedPathBlock + template[pathCloseBrace+1:], nil return nil
} }
func findMatchingBrace(input string, openBraceIndex int) (int, error) { func getObject(parent map[string]interface{}, key string) (map[string]interface{}, error) {
depth := 0 value, ok := parent[key]
inString := false if !ok {
escaped := false return nil, fmt.Errorf("missing key %q", key)
for index := openBraceIndex; index < len(input); index++ {
char := input[index]
if inString {
if escaped {
escaped = false
continue
}
if char == '\\' {
escaped = true
continue
}
if char == '"' {
inString = false
}
continue
} }
if char == '"' { obj, ok := value.(map[string]interface{})
inString = true if !ok {
continue return nil, fmt.Errorf("key %q is not an object", key)
} }
switch char { return obj, nil
case '{':
depth++
case '}':
depth--
if depth == 0 {
return index, nil
}
}
}
return -1, fmt.Errorf("could not find matching brace")
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff