Compare commits

..

30 Commits

Author SHA1 Message Date
Bernhard B.
5c0bd056a7
Merge pull request #842 from Gara-Dorta/receive-swagger
Add swagger definitions for the receive end point
2026-05-17 22:35:43 +02:00
Bernhard B.
ad5d3b76db
Merge pull request #849 from Gara-Dorta/check-docs-are-updated
Check docs are updated
2026-05-17 22:24:09 +02:00
Gara Dorta
0aaab36e5f Enable read of repo 2026-05-15 19:50:35 +02:00
Gara Dorta
44ac16d49c check docs before a release 2026-05-15 19:47:51 +02:00
Gara Dorta
e8c8b54d52 Merge branch 'master' into check-docs-are-updated 2026-05-15 19:41:52 +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
Gara Dorta
82f7151212 fix: download the schemas from signal-cli's fork 2026-05-10 23:23:39 +01:00
Gara Dorta
e68cabf88f fix: the docs regarless of architecture 2026-05-10 22:18:07 +01:00
Gara Dorta
4bbadbf29e fix: download schemas from signal-cli releases 2026-05-10 22:14:19 +01:00
Gara Dorta
b37aac4d5f Merge branch 'master' into receive-swagger 2026-05-10 22:12:48 +01:00
Gara Dorta
16c3214862 Add update docs ci 2026-05-06 12:40:34 +01:00
Era Dorta
a168cf5547 Simplify the instructions in the readme 2026-05-04 14:59:48 +01:00
Era Dorta
1f222d7261 Add receive definitions to the docs files 2026-05-04 14:09:43 +01:00
Era Dorta
6ac432b509 Update script to also modify the json file
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 14:09:12 +01:00
Era Dorta
948a522ad8 Add back json generation 2026-05-04 13:44:20 +01:00
Era Dorta
6fa06f9611 Add back swagger.json file 2026-05-04 13:39:43 +01:00
Era Dorta
eda99213a7 fix: remove swagger.json and swagger.yaml files 2026-05-04 13:36:25 +01:00
Era Dorta
2ac55eec07 Split comments in docker build 2026-05-04 13:02:51 +01:00
Era Dorta
cb5e64a6d5 Add the schemas before building the binary 2026-05-04 13:00:45 +01:00
Era Dorta
0a7c53a10a fix: docs paths in docker build 2026-05-04 12:50:49 +01:00
Era Dorta
09252b4b87 Merge branch 'master' into receive-swagger 2026-05-04 12:10:18 +01:00
Era Dorta
0f2c12f28e Use tabs 2026-04-20 00:21:33 +02:00
Era Dorta
e712494d7f Only build docs on x86_64 2026-04-20 00:20:37 +02:00
Era Dorta
e0af0091c9 Add receive v1 json schemas to swagger
This is done with a custom script to embed the schemas from signal-cli into the docs.go file
2026-04-20 00:07:59 +02:00
9 changed files with 9219 additions and 8891 deletions

28
.github/workflows/check-docs.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Check Generated Docs
on:
workflow_call:
permissions:
contents: read
jobs:
check-docs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.0.0
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Regenerate docs
working-directory: src
run: go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault
- name: Fail if docs are out of date
run: |
git diff --exit-code -- src/docs/docs.go src/docs/swagger.json src/docs/swagger.yaml

View File

@ -7,12 +7,16 @@ on:
description: 'Version' description: 'Version'
required: true required: true
permissions: {} permissions:
contents: read
jobs: jobs:
check-docs:
uses: ./.github/workflows/check-docs.yml
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: check-docs
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0

View File

@ -7,12 +7,16 @@ on:
description: 'Version' description: 'Version'
required: true required: true
permissions: {} permissions:
contents: read
jobs: jobs:
check-docs:
uses: ./.github/workflows/check-docs.yml
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: check-docs
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0

View File

@ -92,10 +92,21 @@ COPY src/main.go /tmp/signal-cli-rest-api-src/
COPY src/go.mod /tmp/signal-cli-rest-api-src/ COPY src/go.mod /tmp/signal-cli-rest-api-src/
COPY src/go.sum /tmp/signal-cli-rest-api-src/ COPY src/go.sum /tmp/signal-cli-rest-api-src/
COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/ COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/
COPY src/docs/add_v1_receive_schemas.go /tmp/signal-cli-rest-api-src/docs/add_v1_receive_schemas.go
RUN ls -la /tmp/signal-cli-rest-api-src
# build the docs
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault --outputTypes "go,json"
# manually add the json schemas for the receive V1 endpoint to the docs
RUN cd /tmp/signal-cli-rest-api-src/docs \
&& wget https://github.com/Gara-Dorta/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz \
&& mkdir signal-cli-schemas \
&& tar xf signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz -C signal-cli-schemas \
&& go run add_v1_receive_schemas.go signal-cli-schemas
# build signal-cli-rest-api # build signal-cli-rest-api
RUN ls -la /tmp/signal-cli-rest-api-src
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v

View File

@ -4,44 +4,64 @@ These files are generated using the [swaggo/swag](https://github.com/swaggo/swag
There are two steps, first generating the docs and then running the web server. There are two steps, first generating the docs and then running the web server.
## Generating the docs ## With docker compose (recommended)
Regenerate the files with your local source code changes. 1. Build the docs
```bash
docker compose build
```
2. Serve the docs
```bash
docker compose up
```
3. Go to http://127.0.0.1:8080/swagger/index.html to view the docs
* If you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html
## Locally
Install [go](https://go.dev/).
### Generating the docs
1. Set the current working dir to `src` 1. Set the current working dir to `src`
```bash ```bash
cd src cd src
``` ```
1. Run swag to generate the docs 1. Run swag to generate the docs
* Option 1, via docker * Option 1, via go
```bash ```bash
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json"
``` ```
* Option 2, install swag and run the command line tool * Option 2, directly with swag
```bash ```bash
swag init --requiredByDefault swag init --requiredByDefault --outputTypes "go,json"
```
* Option 3, swag via docker
```bash
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault --outputTypes "go,json"
```
1. Set the current working dir to `src/docs`
```bash
cd docs
```
1. Add the signal-cli receive V1 schemas
* Download the `signal-cli-x.y.z-json-schemas.tar.gz` schema files from https://github.com/Gara-Dorta/signal-cli/releases
* Extract the files
* Run the script to add the schemas
```bash
go run add_v1_receive_schemas.go ./path-to-signal-cli-json-schema-folder
``` ```
## Run the web server ### Run the web server
Run the web server to visualize the generated docs. Run the web server to visualize the generated docs.
1. Run the main script 1. Navigate to the `src` folder
* Option 1, via docker, run the command at the root of the repository
```bash
docker compose up
```
* Option 2, install go and run the command line tool
```bash ```bash
cd src cd src
``` ```
1. Run the main script
```bash ```bash
go run main.go go run main.go
``` ```
1. Go to http://127.0.0.1:8080/swagger/index.html
## Navigate to the docs
The docs are served at: http://127.0.0.1:8080/swagger/index.html
When serving with docker, if you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html

View File

@ -0,0 +1,327 @@
//go:build ignore
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
_ "github.com/bbernhard/signal-cli-rest-api/docs"
)
const (
goDocsPath = "docs.go"
jsonDocsPath = "swagger.json"
openMarker = "const docTemplate = `"
closeMarker = "`\n\n// SwaggerInfo"
schemesTemplateValue = "{{ marshal .Schemes }}"
schemesPlaceholderToken = "__SWAG_SCHEMES_PLACEHOLDER__"
receivePrefix = "receive."
receivePathKey = "/v1/receive/{number}"
receiveWrapper = "data.Message"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: go run update_receive_docs.go <receiveDir>\n")
os.Exit(1)
}
receiveDir := os.Args[1]
if err := run(receiveDir); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run(receiveDir string) error {
definitions := make(map[string]interface{})
titleByFile, err := addReceiveSchemas(definitions, receiveDir)
if err != nil {
return err
}
updateReceiveSchemaRefs(definitions, titleByFile)
addEnvelopeWrapperDefinition(definitions)
if err := updateDocsGo(definitions); err != nil {
return err
}
if err := updateSwaggerJSON(definitions); err != nil {
return err
}
fmt.Printf("updated %s\n", goDocsPath)
fmt.Printf("updated %s\n", jsonDocsPath)
return nil
}
func updateDocsGo(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(goDocsPath)
if err != nil {
return fmt.Errorf("read %s: %w", goDocsPath, err)
}
template, templateStart, templateEnd, err := extractDocTemplate(string(content))
if err != nil {
return err
}
updatedTemplate, err := updateJSONDocument(toValidJson(template), receiveDefinitions)
if err != nil {
return err
}
updatedTemplate = encodeForGoRawString(updatedTemplate)
updated := string(content[:templateStart]) + updatedTemplate + string(content[templateEnd:])
if err := os.WriteFile(goDocsPath, []byte(updated), 0644); err != nil {
return fmt.Errorf("write %s: %w", goDocsPath, err)
}
return nil
}
func updateSwaggerJSON(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(jsonDocsPath)
if err != nil {
return fmt.Errorf("read %s: %w", jsonDocsPath, err)
}
updated, err := updateJSONDocument(string(content), receiveDefinitions)
if err != nil {
return err
}
if err := os.WriteFile(jsonDocsPath, []byte(updated), 0644); err != nil {
return fmt.Errorf("write %s: %w", jsonDocsPath, err)
}
return nil
}
func extractDocTemplate(content string) (string, int, int, error) {
start := strings.Index(content, openMarker)
if start == -1 {
return "", -1, -1, fmt.Errorf("could not find docTemplate start in %s", goDocsPath)
}
start += len(openMarker)
endOffset := strings.Index(content[start:], closeMarker)
if endOffset == -1 {
return "", -1, -1, fmt.Errorf("could not find docTemplate end in %s", goDocsPath)
}
end := start + endOffset
return content[start:end], start, end, nil
}
func toValidJson(content string) string {
content = strings.ReplaceAll(content, "` + \"`\" + `", "`")
content = strings.Replace(content, schemesTemplateValue, `"`+schemesPlaceholderToken+`"`, 1)
return content
}
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 {
return "", err
}
raw, err := json.MarshalIndent(document, "", " ")
if err != nil {
return "", fmt.Errorf("marshal document: %w", err)
}
return string(raw), nil
}
func addReceiveSchemas(definitions map[string]interface{}, receiveDir string) (map[string]string, error) {
entries, err := os.ReadDir(receiveDir)
if err != nil {
return nil, fmt.Errorf("read %s: %w", receiveDir, err)
}
files := make([]string, 0)
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".schema.json") {
continue
}
files = append(files, entry.Name())
}
sort.Strings(files)
titleByFile := make(map[string]string, len(files))
for _, name := range files {
fullPath := filepath.Join(receiveDir, name)
data, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("read schema file %s: %w", fullPath, err)
}
var schemaObj map[string]interface{}
if err := json.Unmarshal(data, &schemaObj); err != nil {
return nil, fmt.Errorf("parse schema file %s: %w", fullPath, err)
}
title, ok := schemaObj["title"].(string)
if !ok || strings.TrimSpace(title) == "" {
return nil, fmt.Errorf("schema file %s is missing title", fullPath)
}
titleByFile[name] = title
definitions[receivePrefix+title] = removeSchemaKeysRecursive(schemaObj, "")
}
return titleByFile, nil
}
func removeSchemaKeysRecursive(value interface{}, parentKey string) interface{} {
switch typed := value.(type) {
case map[string]interface{}:
updated := make(map[string]interface{}, len(typed))
for key, item := range typed {
if key == "$schema" || key == "$id" {
continue
}
if key == "title" && parentKey != "properties" {
continue
}
updated[key] = removeSchemaKeysRecursive(item, key)
}
return updated
case []interface{}:
updated := make([]interface{}, len(typed))
for idx, item := range typed {
updated[idx] = removeSchemaKeysRecursive(item, parentKey)
}
return updated
default:
return value
}
}
func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) {
for key, value := range definitions {
if !strings.HasPrefix(key, receivePrefix) {
continue
}
definitions[key] = rewriteSchemaRefs(value, titleByFile)
}
}
func rewriteSchemaRefs(value interface{}, titleByFile map[string]string) interface{} {
switch typed := value.(type) {
case map[string]interface{}:
updated := make(map[string]interface{}, len(typed))
for k, v := range typed {
if k == "$ref" {
if refValue, ok := v.(string); ok {
if strings.HasSuffix(refValue, ".schema.json") {
base := filepath.Base(refValue)
if title, exists := titleByFile[base]; exists {
updated[k] = "#/definitions/" + receivePrefix + title
continue
}
}
}
}
updated[k] = rewriteSchemaRefs(v, titleByFile)
}
return updated
case []interface{}:
updated := make([]interface{}, len(typed))
for idx, item := range typed {
updated[idx] = rewriteSchemaRefs(item, titleByFile)
}
return updated
default:
return value
}
}
func addEnvelopeWrapperDefinition(definitions map[string]interface{}) {
definitions[receiveWrapper] = map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"account": map[string]interface{}{"type": "string"},
"envelope": map[string]interface{}{"$ref": "#/definitions/receive.MessageEnvelope"},
},
"required": []interface{}{"account", "envelope"},
}
}
func applyReceiveSchemaUpdates(document map[string]interface{}, receiveDefinitions map[string]interface{}) error {
definitions, err := getObject(document, "definitions")
if err != nil {
return err
}
for key := range definitions {
if strings.HasPrefix(key, receivePrefix) || key == receiveWrapper {
delete(definitions, key)
}
}
for key, value := range receiveDefinitions {
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
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff