Skip to content

Commit b7baa59

Browse files
authored
Fix handling of multiline strings in @Guide macro (#11)
1 parent 16868bb commit b7baa59

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

Sources/AnyLanguageModelMacros/GenerableMacro.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,10 +538,25 @@ public struct GenerableMacro: MemberMacro, ExtensionMacro {
538538
guidesArray = "[\(guides.joined(separator: ", "))]"
539539
}
540540

541+
// Escape the description string so it can be safely embedded in generated code.
542+
// Multi-line strings need newlines converted to \n escape sequences,
543+
// and special characters (backslashes, quotes) must be escaped.
544+
let escapedDescription: String
545+
if let desc = prop.guideDescription {
546+
let escaped =
547+
desc
548+
.replacingOccurrences(of: "\\", with: "\\\\") // Escape backslashes first
549+
.replacingOccurrences(of: "\"", with: "\\\"") // Escape quotes
550+
.replacingOccurrences(of: "\n", with: "\\n") // Convert newlines to escape sequences
551+
escapedDescription = "\"\(escaped)\""
552+
} else {
553+
escapedDescription = "nil"
554+
}
555+
541556
return """
542557
GenerationSchema.Property(
543558
name: "\(prop.name)",
544-
description: \(prop.guideDescription.map { "\"\($0)\"" } ?? "nil"),
559+
description: \(escapedDescription),
545560
type: \(prop.type).self,
546561
guides: \(guidesArray)
547562
)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Testing
2+
import AnyLanguageModel
3+
import Foundation
4+
5+
@Generable
6+
private struct TestStructWithMultilineDescription {
7+
@Guide(
8+
description: """
9+
This is a multi-line description.
10+
It spans multiple lines.
11+
"""
12+
)
13+
var field: String
14+
}
15+
16+
@Generable
17+
private struct TestStructWithSpecialCharacters {
18+
@Guide(description: "A description with \"quotes\" and backslashes \\")
19+
var field: String
20+
}
21+
22+
@Generable
23+
private struct TestStructWithNewlines {
24+
@Guide(description: "Line 1\nLine 2\nLine 3")
25+
var field: String
26+
}
27+
28+
@Suite("Generable Macro")
29+
struct GenerableMacroTests {
30+
@Test func multilineGuideDescription() async throws {
31+
let schema = TestStructWithMultilineDescription.generationSchema
32+
let encoder = JSONEncoder()
33+
let jsonData = try encoder.encode(schema)
34+
35+
// Verify that the schema can be encoded without errors (no unterminated strings)
36+
#expect(jsonData.count > 0)
37+
38+
// Verify it can be decoded back
39+
let decoder = JSONDecoder()
40+
let decodedSchema = try decoder.decode(GenerationSchema.self, from: jsonData)
41+
#expect(decodedSchema.debugDescription.contains("object"))
42+
}
43+
44+
@Test func guideDescriptionWithSpecialCharacters() async throws {
45+
let schema = TestStructWithSpecialCharacters.generationSchema
46+
let encoder = JSONEncoder()
47+
let jsonData = try encoder.encode(schema)
48+
let jsonString = String(data: jsonData, encoding: .utf8)!
49+
50+
// Verify the special characters are escaped
51+
#expect(jsonString.contains(#"\\\"quotes\\\""#))
52+
#expect(jsonString.contains(#"backslashes \\\\"#))
53+
54+
// Verify roundtrip encoding/decoding works
55+
let decoder = JSONDecoder()
56+
let decodedSchema = try decoder.decode(GenerationSchema.self, from: jsonData)
57+
#expect(decodedSchema.debugDescription.contains("object"))
58+
}
59+
60+
@Test func guideDescriptionWithNewlines() async throws {
61+
let schema = TestStructWithNewlines.generationSchema
62+
let encoder = JSONEncoder()
63+
let jsonData = try encoder.encode(schema)
64+
65+
// Verify that the schema can be encoded without errors
66+
#expect(jsonData.count > 0)
67+
68+
// Verify roundtrip encoding/decoding works
69+
let decoder = JSONDecoder()
70+
let decodedSchema = try decoder.decode(GenerationSchema.self, from: jsonData)
71+
#expect(decodedSchema.debugDescription.contains("object"))
72+
}
73+
}

0 commit comments

Comments
 (0)