refactor(loader): Throw when template not found

This commit is contained in:
Kyle Fuller
2016-11-30 17:07:38 +00:00
parent 63c2b935f7
commit 2ebb79df8b
8 changed files with 156 additions and 10 deletions

View File

@@ -2,6 +2,11 @@
## Master ## Master
### Breaking
- `Loader`s will now throw a `TemplateDoesNotExist` error when a template
is not found.
### Enhancements ### Enhancements
- `FileSystemLoader` will now ensure that template paths are within the base - `FileSystemLoader` will now ensure that template paths are within the base

14
Sources/Errors.swift Normal file
View File

@@ -0,0 +1,14 @@
public class TemplateDoesNotExist: Error, CustomStringConvertible {
let templateNames: [String]
let loader: Loader
public init(templateNames: [String], loader: Loader) {
self.templateNames = templateNames
self.loader = loader
}
public var description: String {
let templates = templateNames.joined(separator: ", ")
return "Template named `\(templates)` does not exist in loader \(loader)"
}
}

View File

@@ -27,10 +27,7 @@ class IncludeNode : NodeType {
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string") throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
} }
guard let template = try loader.loadTemplate(name: templateName) else { let template = try loader.loadTemplate(name: templateName)
throw TemplateSyntaxError("'\(templateName)' template not found")
}
return try template.render(context) return try template.render(context)
} }
} }

View File

@@ -67,9 +67,7 @@ class ExtendsNode : NodeType {
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string") throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
} }
guard let template = try loader.loadTemplate(name: templateName) else { let template = try loader.loadTemplate(name: templateName)
throw TemplateSyntaxError("'\(templateName)' template not found")
}
let blockContext: BlockContext let blockContext: BlockContext
if let context = context[BlockContext.contextKey] as? BlockContext { if let context = context[BlockContext.contextKey] as? BlockContext {

101
Sources/Loader.swift Normal file
View File

@@ -0,0 +1,101 @@
import Foundation
import PathKit
public protocol Loader {
func loadTemplate(name: String) throws -> Template
func loadTemplate(names: [String]) throws -> Template
}
extension Loader {
func loadTemplate(names: [String]) throws -> Template {
for name in names {
do {
return try loadTemplate(name: name)
} catch is TemplateDoesNotExist {
continue
} catch {
throw error
}
}
throw TemplateDoesNotExist(templateNames: names, loader: self)
}
}
// A class for loading a template from disk
public class FileSystemLoader: Loader, CustomStringConvertible {
public let paths: [Path]
public init(paths: [Path]) {
self.paths = paths
}
public init(bundle: [Bundle]) {
self.paths = bundle.map {
return Path($0.bundlePath)
}
}
public var description: String {
return "FileSystemLoader(\(paths))"
}
public func loadTemplate(name: String) throws -> Template {
for path in paths {
let templatePath = try path.safeJoin(path: Path(name))
if !templatePath.exists {
continue
}
return try Template(path: templatePath)
}
throw TemplateDoesNotExist(templateNames: [name], loader: self)
}
public func loadTemplate(names: [String]) throws -> Template {
for path in paths {
for templateName in names {
let templatePath = try path.safeJoin(path: Path(templateName))
if templatePath.exists {
return try Template(path: templatePath)
}
}
}
throw TemplateDoesNotExist(templateNames: names, loader: self)
}
}
extension Path {
func safeJoin(path: Path) throws -> Path {
let newPath = self + path
if !newPath.absolute().description.hasPrefix(absolute().description) {
throw SuspiciousFileOperation(basePath: self, path: newPath)
}
return newPath
}
}
class SuspiciousFileOperation: Error {
let basePath: Path
let path: Path
init(basePath: Path, path: Path) {
self.basePath = basePath
self.path = path
}
var description: String {
return "Path `\(path)` is located outside of base path `\(basePath)`"
}
}

View File

@@ -45,7 +45,7 @@ func testInclude() {
do { do {
_ = try node.render(Context(dictionary: ["loader": loader])) _ = try node.render(Context(dictionary: ["loader": loader]))
} catch { } catch {
try expect("\(error)".hasPrefix("'unknown.html' template not found")).to.beTrue() try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue()
} }
} }

View File

@@ -11,13 +11,13 @@ func testInheritence() {
$0.it("can inherit from another template") { $0.it("can inherit from another template") {
let context = Context(dictionary: ["loader": loader]) let context = Context(dictionary: ["loader": loader])
let template = try loader.loadTemplate(name: "child.html") let template = try loader.loadTemplate(name: "child.html")
try expect(try template?.render(context)) == "Header\nChild" try expect(try template.render(context)) == "Header\nChild"
} }
$0.it("can inherit from another template inheriting from another template") { $0.it("can inherit from another template inheriting from another template") {
let context = Context(dictionary: ["loader": loader]) let context = Context(dictionary: ["loader": loader])
let template = try loader.loadTemplate(name: "child-child.html") let template = try loader.loadTemplate(name: "child-child.html")
try expect(try template?.render(context)) == "Child Child Header\nChild" try expect(try template.render(context)) == "Child Child Header\nChild"
} }
} }
} }

View File

@@ -0,0 +1,31 @@
import Spectre
import Stencil
import PathKit
func testTemplateLoader() {
describe("FileSystemLoader") {
let path = Path(#file) + ".." + "fixtures"
let loader = FileSystemLoader(paths: [path])
$0.it("errors when a template cannot be found") {
try expect(try loader.loadTemplate(name: "unknown.html")).toThrow()
}
$0.it("errors when an array of templates cannot be found") {
try expect(try loader.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
}
$0.it("can load a template from a file") {
_ = try loader.loadTemplate(name: "test.html")
}
$0.it("errors when loading absolute file outside of the selected path") {
try expect(try loader.loadTemplate(name: "/etc/hosts")).toThrow()
}
$0.it("errors when loading relative file outside of the selected path") {
try expect(try loader.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
}
}
}