Use json.RawMessage for document data content

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-30 11:28:27 +04:00
parent f29b9e22d3
commit be189693ae
4 changed files with 59 additions and 47 deletions

View File

@@ -17,6 +17,7 @@ package docgen
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"html"
"html/template"
@@ -194,7 +195,7 @@ type (
DocumentData struct {
Title string
Content string // ProseMirror/Tiptap document JSON; use ProseMirrorJSONToHTML for HTML
Content json.RawMessage // ProseMirror/Tiptap document JSON; use ProseMirrorJSONToHTML for HTML
Major int
Minor int
Classification Classification
@@ -319,8 +320,8 @@ const (
// ProseMirrorJSONToHTML converts ProseMirror/Tiptap document JSON to an HTML fragment.
// On parse or render failure it returns a single escaped paragraph with the raw input.
func ProseMirrorJSONToHTML(content string) template.HTML {
s := strings.TrimSpace(content)
func ProseMirrorJSONToHTML(content json.RawMessage) template.HTML {
s := strings.TrimSpace(string(content))
if s == "" {
return template.HTML("")
}

View File

@@ -15,6 +15,7 @@
package docgen
import (
"encoding/json"
"html/template"
"strings"
"testing"
@@ -37,8 +38,10 @@ func TestRenderHTML(t *testing.T) {
{
name: "basic document with all fields",
data: DocumentData{
Title: "Test Document",
Content: `{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Main Title"}]},{"type":"paragraph","content":[{"type":"text","text":"This is "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"},{"type":"text","text":" text with "},{"type":"text","marks":[{"type":"italic"}],"text":"italic"},{"type":"text","text":" formatting."}]}]}`,
Title: "Test Document",
Content: json.RawMessage(
[]byte(`{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Main Title"}]},{"type":"paragraph","content":[{"type":"text","text":"This is "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"},{"type":"text","text":" text with "},{"type":"text","marks":[{"type":"italic"}],"text":"italic"},{"type":"text","text":" formatting."}]}]}`),
),
Major: 1,
Classification: ClassificationPublic,
Approvers: []string{"John Doe"},
@@ -67,7 +70,7 @@ func TestRenderHTML(t *testing.T) {
name: "document with HTML characters that need escaping",
data: DocumentData{
Title: "Test & <Script> Title",
Content: "Normal markdown content",
Content: json.RawMessage([]byte("Normal markdown content")),
Approvers: []string{"John <script>alert('xss')</script> Doe"},
Signatures: []SignatureData{
{
@@ -90,19 +93,21 @@ func TestRenderHTML(t *testing.T) {
name: "document with prosemirror content",
data: DocumentData{
Title: "ProseMirror Test",
Content: `{"type":"doc","content":[` +
`{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Section 1"}]},` +
`{"type":"bulletList","content":[` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` +
`]},` +
`{"type":"paragraph","content":[` +
`{"type":"text","marks":[{"type":"bold"}],"text":"Bold text"},` +
`{"type":"text","text":" and "},` +
`{"type":"text","marks":[{"type":"italic"}],"text":"italic text"}` +
`]},` +
`{"type":"codeBlock","content":[{"type":"text","text":"code block"}]}` +
`]}`,
Content: json.RawMessage([]byte(
`{"type":"doc","content":[` +
`{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Section 1"}]},` +
`{"type":"bulletList","content":[` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` +
`]},` +
`{"type":"paragraph","content":[` +
`{"type":"text","marks":[{"type":"bold"}],"text":"Bold text"},` +
`{"type":"text","text":" and "},` +
`{"type":"text","marks":[{"type":"italic"}],"text":"italic text"}` +
`]},` +
`{"type":"codeBlock","content":[{"type":"text","text":"code block"}]}` +
`]}`,
)),
},
wantContains: []string{
"<h2>Section 1</h2>",
@@ -189,8 +194,10 @@ func TestRenderHTML(t *testing.T) {
func TestRenderHTML_ErrorHandling(t *testing.T) {
data := DocumentData{
Title: "Valid Document",
Content: `{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Valid content"}]}]}`,
Title: "Valid Document",
Content: json.RawMessage(
[]byte(`{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Valid content"}]}]}`),
),
}
result, err := RenderHTML(data)
@@ -226,20 +233,20 @@ func TestTemplateFunctions(t *testing.T) {
})
t.Run("ProseMirrorJSONToHTML", func(t *testing.T) {
result := ProseMirrorJSONToHTML(
`{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"plain "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"}]}]}`,
)
result := ProseMirrorJSONToHTML(json.RawMessage(
[]byte(`{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"plain "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"}]}]}`),
))
assert.Contains(t, string(result), "<strong>bold</strong>")
result = ProseMirrorJSONToHTML(
`{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"simple text"}]}]}`,
)
result = ProseMirrorJSONToHTML(json.RawMessage(
[]byte(`{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"simple text"}]}]}`),
))
assert.Contains(t, string(result), "<p>simple text</p>")
result = ProseMirrorJSONToHTML("**not** json")
result = ProseMirrorJSONToHTML(json.RawMessage([]byte("**not** json")))
assert.Contains(t, string(result), "<p>**not** json</p>")
result = ProseMirrorJSONToHTML("")
result = ProseMirrorJSONToHTML(nil)
assert.Equal(t, template.HTML(""), result)
})
}
@@ -343,7 +350,7 @@ func TestProseMirrorContentRendering(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
data := DocumentData{
Title: "ProseMirror Test",
Content: tt.content,
Content: json.RawMessage([]byte(tt.content)),
}
result, err := RenderHTML(data)
@@ -413,7 +420,7 @@ func TestLargeContent(t *testing.T) {
data := DocumentData{
Title: "Large Document",
Content: largeContent.String(),
Content: json.RawMessage([]byte(largeContent.String())),
}
result, err := RenderHTML(data)
@@ -427,20 +434,22 @@ func BenchmarkGenerateHTML(b *testing.B) {
data := DocumentData{
Title: "Benchmark Document",
Content: `{"type":"doc","content":[` +
`{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Title"}]},` +
`{"type":"paragraph","content":[` +
`{"type":"text","text":"This is "},` +
`{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` +
`{"type":"text","text":" text with "},` +
`{"type":"text","marks":[{"type":"italic"}],"text":"italic"},` +
`{"type":"text","text":" formatting."}` +
`]},` +
`{"type":"bulletList","content":[` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` +
`]}` +
`]}`,
Content: json.RawMessage([]byte(
`{"type":"doc","content":[` +
`{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Title"}]},` +
`{"type":"paragraph","content":[` +
`{"type":"text","text":"This is "},` +
`{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` +
`{"type":"text","text":" text with "},` +
`{"type":"text","marks":[{"type":"italic"}],"text":"italic"},` +
`{"type":"text","text":" formatting."}` +
`]},` +
`{"type":"bulletList","content":[` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` +
`{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` +
`]}` +
`]}`,
)),
Major: 1,
Classification: ClassificationPublic,
Approvers: []string{"John Doe"},

View File

@@ -1994,7 +1994,7 @@ func exportDocumentPDF(
docData := docgen.DocumentData{
Title: version.Title,
Content: version.Content,
Content: json.RawMessage([]byte(version.Content)),
Major: version.Major,
Minor: version.Minor,
Classification: classification,

View File

@@ -16,10 +16,12 @@ package trust
import (
"context"
"encoding/json"
"fmt"
"io"
"errors"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/docgen"
@@ -245,7 +247,7 @@ func (s *DocumentService) exportPDFData(
docData := docgen.DocumentData{
Title: version.Title,
Content: version.Content,
Content: json.RawMessage([]byte(version.Content)),
Major: version.Major,
Minor: version.Minor,
Classification: classification,