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

View File

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

View File

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

View File

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