mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-05-19 13:34:19 +00:00
Compare commits
1 Commits
98d74de18b
...
68ecd0df66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68ecd0df66 |
@ -8,21 +8,21 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goDocsPath = "docs.go"
|
goDocsPath = "docs.go"
|
||||||
jsonDocsPath = "swagger.json"
|
jsonDocsPath = "swagger.json"
|
||||||
openMarker = "const docTemplate = `"
|
openMarker = "const docTemplate = `"
|
||||||
closeMarker = "`\n\n// SwaggerInfo"
|
closeMarker = "`\n\n// SwaggerInfo"
|
||||||
schemesTemplateValue = "{{ marshal .Schemes }}"
|
definitionsKey = `"definitions": {`
|
||||||
schemesPlaceholderToken = "__SWAG_SCHEMES_PLACEHOLDER__"
|
receivePrefix = "receive."
|
||||||
receivePrefix = "receive."
|
receivePathKey = `"/v1/receive/{number}":`
|
||||||
receivePathKey = "/v1/receive/{number}"
|
receiveWrapper = "data.Message"
|
||||||
receiveWrapper = "data.Message"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -46,15 +46,22 @@ func run(receiveDir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReceiveSchemaRefs(definitions, titleByFile)
|
if err := updateReceiveSchemaRefs(definitions, titleByFile); err != nil {
|
||||||
|
|
||||||
addEnvelopeWrapperDefinition(definitions)
|
|
||||||
|
|
||||||
if err := updateDocsGo(definitions); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateSwaggerJSON(definitions); err != nil {
|
addEnvelopeWrapperDefinition(definitions)
|
||||||
|
|
||||||
|
managedDefinitions, err := renderManagedDefinitions(definitions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateDocsGo(managedDefinitions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateSwaggerJSON(managedDefinitions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,24 +70,23 @@ func run(receiveDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDocsGo(receiveDefinitions map[string]interface{}) error {
|
func updateDocsGo(managedDefinitions string) 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, templateStart, templateEnd, err := extractDocTemplate(string(content))
|
template, err := extractDocTemplate(string(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedTemplate, err := updateJSONDocument(toValidJson(template), receiveDefinitions)
|
updatedTemplate, err := applyReceiveSchemaUpdates(template, managedDefinitions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updatedTemplate = encodeForGoRawString(updatedTemplate)
|
|
||||||
|
|
||||||
updated := string(content[:templateStart]) + updatedTemplate + string(content[templateEnd:])
|
updated := strings.Replace(string(content), template, updatedTemplate, 1)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -88,13 +94,13 @@ func updateDocsGo(receiveDefinitions map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSwaggerJSON(receiveDefinitions map[string]interface{}) error {
|
func updateSwaggerJSON(managedDefinitions string) 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 := updateJSONDocument(string(content), receiveDefinitions)
|
updated, err := applyReceiveSchemaUpdates(string(content), managedDefinitions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -106,50 +112,74 @@ func updateSwaggerJSON(receiveDefinitions map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractDocTemplate(content string) (string, int, int, error) {
|
func applyReceiveSchemaUpdates(content string, managedDefinitions string) (string, 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 "", -1, -1, fmt.Errorf("could not find docTemplate start in %s", goDocsPath)
|
return "", 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 "", -1, -1, fmt.Errorf("could not find docTemplate end in %s", goDocsPath)
|
return "", fmt.Errorf("could not find docTemplate end in %s", goDocsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
end := start + endOffset
|
return content[start : start+endOffset], nil
|
||||||
return content[start:end], start, end, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toValidJson(content string) string {
|
func definitionsBounds(template string) (int, int, error) {
|
||||||
content = strings.ReplaceAll(content, "` + \"`\" + `", "`")
|
definitionsIndex := strings.Index(template, definitionsKey)
|
||||||
content = strings.Replace(content, schemesTemplateValue, `"`+schemesPlaceholderToken+`"`, 1)
|
if definitionsIndex == -1 {
|
||||||
return content
|
return -1, -1, fmt.Errorf("could not find definitions block in docTemplate")
|
||||||
}
|
|
||||||
|
|
||||||
func encodeForGoRawString(content string) string {
|
|
||||||
content = strings.ReplaceAll(content, "`", "` + \"`\" + `")
|
|
||||||
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 {
|
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) {
|
||||||
|
braceIndex, closingBraceIndex, err := definitionsBounds(template)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := json.MarshalIndent(document, "", " ")
|
definitionsBlock := template[braceIndex : closingBraceIndex+1]
|
||||||
if err != nil {
|
if strings.Contains(definitionsBlock, `"`+receiveWrapper+`"`) || strings.Contains(definitionsBlock, `"`+receivePrefix) {
|
||||||
return "", fmt.Errorf("marshal document: %w", err)
|
return "", fmt.Errorf("definitions already contain receive entries; run swag init first")
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(raw), nil
|
inner := strings.TrimSpace(template[braceIndex+1 : closingBraceIndex])
|
||||||
|
|
||||||
|
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) {
|
||||||
@ -217,13 +247,15 @@ func removeSchemaKeysRecursive(value interface{}, parentKey string) interface{}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) {
|
func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) error {
|
||||||
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{} {
|
||||||
@ -268,60 +300,110 @@ func addEnvelopeWrapperDefinition(definitions map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyReceiveSchemaUpdates(document map[string]interface{}, receiveDefinitions map[string]interface{}) error {
|
func renderManagedDefinitions(definitions map[string]interface{}) (string, error) {
|
||||||
definitions, err := getObject(document, "definitions")
|
keys := make([]string, 0, len(definitions))
|
||||||
if err != nil {
|
for key := range definitions {
|
||||||
return err
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
parts := make([]string, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
raw, err := json.MarshalIndent(definitions[key], "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("marshal definition %s: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key := range definitions {
|
return strings.Join(parts, ",\n"), nil
|
||||||
if strings.HasPrefix(key, receivePrefix) || key == receiveWrapper {
|
}
|
||||||
delete(definitions, key)
|
|
||||||
|
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 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pathBlock := template[pathOpenBrace : pathCloseBrace+1]
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatchingBrace(input string, openBraceIndex int) (int, error) {
|
||||||
|
depth := 0
|
||||||
|
inString := false
|
||||||
|
escaped := false
|
||||||
|
|
||||||
|
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 == '"' {
|
||||||
|
inString = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch char {
|
||||||
|
case '{':
|
||||||
|
depth++
|
||||||
|
case '}':
|
||||||
|
depth--
|
||||||
|
if depth == 0 {
|
||||||
|
return index, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range receiveDefinitions {
|
return -1, fmt.Errorf("could not find matching brace")
|
||||||
definitions[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
paths, err := getObject(document, "paths")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
receivePath, err := getObject(paths, receivePathKey)
|
|
||||||
if err != nil {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
response200["schema"] = map[string]interface{}{
|
|
||||||
"$ref": "#/definitions/" + receiveWrapper,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObject(parent map[string]interface{}, key string) (map[string]interface{}, error) {
|
|
||||||
value, ok := parent[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("missing key %q", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, ok := value.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("key %q is not an object", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
6375
src/docs/docs.go
6375
src/docs/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user