Skip to content

Commit 9bf6683

Browse files
committed
Add app.render() that returns a string
Less efficient than proper streaming, but this is apparently what the Express API gives us...
1 parent 75cdc16 commit 9bf6683

File tree

1 file changed

+84
-79
lines changed

1 file changed

+84
-79
lines changed

Sources/express/Render.swift

Lines changed: 84 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,23 @@ public extension ServerResponse {
4646
return
4747
}
4848

49-
app.render(template: template, options: options, to: self)
49+
app.render(template, options) { errorMaybe, contentMaybe in
50+
if let error = errorMaybe {
51+
self.emit(error: error)
52+
return self.finishRender500IfNecessary()
53+
}
54+
55+
guard let content = contentMaybe else {
56+
return self.sendStatus(204)
57+
}
58+
59+
// Wow, this is harder than it looks. we may want to consider a MIMEType
60+
// object as a value :-)
61+
// FIXME: also consider extension of template (.html, .vcf etc)
62+
self.setHeader("Content-Type", detectTypeForContent(string: content))
63+
64+
self.send(content)
65+
}
5066
}
5167

5268
fileprivate func finishRender500IfNecessary() {
@@ -57,85 +73,28 @@ public extension ServerResponse {
5773
}
5874

5975
public extension Express {
60-
61-
/**
62-
* Locate the rendering engine for a given path and render it with the options
63-
* that are passed in.
64-
*
65-
* Refer to the ``ServerResponse/render`` method for details.
66-
*
67-
* - Parameters:
68-
* - path: the filesystem path to a template.
69-
* - options: Any options passed to the rendering engine.
70-
* - res: The response the rendering will be sent to.
71-
*/
72-
func render(path: String, options: Any?, to res: ServerResponse) {
73-
let log = self.log
74-
let ext = fs.path.extname(path)
75-
let viewEngine = ext.isEmpty ? defaultEngine : ext
76-
77-
guard let engine = engines[viewEngine] else {
78-
log.error("Did not find view engine for extension: \(viewEngine)")
79-
res.emit(error: ExpressRenderingError.unsupportedViewEngine(viewEngine))
80-
res.finishRender500IfNecessary()
81-
return
82-
}
83-
84-
engine(path, options) { ( results: Any?... ) in
85-
let rc = results.count
86-
let v0 = rc > 0 ? results[0] : nil
87-
let v1 = rc > 1 ? results[1] : nil
88-
89-
if let error = v0 {
90-
res.emit(error: ExpressRenderingError
91-
.templateError(error as? Swift.Error))
92-
log.error("template error:", error)
93-
res.writeHead(500)
94-
res.end()
95-
return
96-
}
97-
98-
guard let result = v1 else { // Hm?
99-
log.warn("template returned no content: \(path) \(results)")
100-
res.writeHead(204)
101-
res.end()
102-
return
103-
}
10476

105-
// TBD: maybe support a stream as a result? (result.pipe(res))
106-
// Or generators, there are many more options.
107-
if !(result is String) {
108-
log.warn("template rendering result is not a String:", result)
109-
}
110-
111-
let s = (result as? String) ?? "\(result)"
112-
113-
// Wow, this is harder than it looks when we want to consider a MIMEType
114-
// object as a value :-)
115-
var setContentType = true
116-
if let oldType = res.getHeader("Content-Type") {
117-
let s = (oldType as? String) ?? String(describing: oldType) // FIXME
118-
setContentType = (s == "httpd/unix-directory") // a hack for Apache
119-
}
120-
121-
if setContentType {
122-
// FIXME: also consider extension of template (.html, .vcf etc)
123-
res.setHeader("Content-Type", detectTypeForContent(string: s))
124-
}
125-
126-
res.writeHead(200)
127-
res.write(s)
128-
res.end()
129-
}
130-
}
131-
13277
/**
13378
* Lookup a template with the given name, locate the rendering engine for it,
13479
* and render it with the options that are passed in.
13580
*
136-
* Refer to the ``ServerResponse/render`` method for details.
81+
* Example:
82+
* ```swift
83+
* app.render("index", [ "title": "Hello World!" ]) { error, html in
84+
* ...
85+
* }
86+
* ```
87+
*
88+
* Assuming your 'views' directory contains an `index.mustache` file, this
89+
* would trigger the Mustache engine to render the template with the given
90+
* dictionary as input.
91+
*
92+
* When no options are passed in, render will fallback to the `view options`
93+
* setting in the application (TODO: merge the two contexts).
13794
*/
138-
func render(template: String, options: Any?, to res: ServerResponse) {
95+
func render(_ template: String, _ options : Any? = nil,
96+
yield: @escaping ( Error?, String? ) -> Void)
97+
{
13998
let log = self.log
14099
let cacheOn = settings.enabled("view cache")
141100
let emptyOpts : [ String : Any ] = [:]
@@ -147,17 +106,15 @@ public extension Express {
147106
let path = view.path
148107
{
149108
log.trace("Using cached view:", template)
150-
return self.render(path: path, options: viewOptions, to: res)
109+
return self.render(path: path, options: viewOptions, yield: yield)
151110
}
152111

153112
let viewType : View.Type = (get("view") as? View.Type) ?? View.self
154113
let view = viewType.init(name: template, options: self)
155114
let name = path.basename(template, path.extname(template))
156115
view.lookup(name) { pathOrNot in
157116
guard let path = pathOrNot else {
158-
res.emit(error: ExpressRenderingError.didNotFindTemplate(template))
159-
res.finishRender500IfNecessary()
160-
return
117+
return yield(ExpressRenderingError.didNotFindTemplate(template), nil)
161118
}
162119

163120
view.path = path // cache path
@@ -168,9 +125,57 @@ public extension Express {
168125
}
169126
}
170127

171-
self.render(path: path, options: viewOptions, to: res)
128+
self.render(path: path, options: viewOptions, yield: yield)
172129
}
173130
}
131+
132+
/**
133+
* Locate the rendering engine for a given path and render it with the options
134+
* that are passed in.
135+
*
136+
* Refer to the ``ServerResponse/render`` method for details.
137+
*
138+
* - Parameters:
139+
* - path: the filesystem path to a template.
140+
* - options: Any options passed to the rendering engine.
141+
* - yield: The result of template being rendered.
142+
*/
143+
func render(path: String, options: Any?,
144+
yield: @escaping ( Error?, String? ) -> Void)
145+
{
146+
let log = self.log
147+
let ext = fs.path.extname(path)
148+
let viewEngine = ext.isEmpty ? defaultEngine : ext
149+
150+
guard let engine = engines[viewEngine] else {
151+
log.error("Did not find view engine for extension: \(viewEngine)")
152+
return yield(ExpressRenderingError.unsupportedViewEngine(viewEngine), nil)
153+
}
154+
155+
engine(path, options) { ( results: Any?... ) in
156+
if let value = results.first, let error = value {
157+
log.error("view engine error:", error)
158+
yield(ExpressRenderingError.templateError(error as? Swift.Error), nil)
159+
return
160+
}
161+
162+
guard let input = results.dropFirst().first, let result = input else {
163+
log.warn("View engine returned no content for:", path, results)
164+
return yield(nil, nil)
165+
}
166+
167+
// TBD: maybe support a stream as a result? (result.pipe(res))
168+
// Or generators, there are many more options.
169+
if !(result is String) {
170+
log.warn("template rendering result is not a String:", result)
171+
assertionFailure("Non-template rendering result \(type(of: result))")
172+
}
173+
174+
let s = (result as? String) ?? "\(result)"
175+
yield(nil, s)
176+
}
177+
}
178+
174179
}
175180

176181

0 commit comments

Comments
 (0)