Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft idea for post deserialization validation #251

Merged
merged 7 commits into from
May 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: GitHub Action for SwiftLint
uses: norio-nomura/[email protected]
- name: GitHub Action for SwiftLint with --strict
uses: norio-nomura/[email protected]
- uses: actions/checkout@v2
- name: SwiftLint
uses: norio-nomura/[email protected]
with:
args: --strict
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
run: |
set -o pipefail
xcodebuild $ACTION $WORKSPACE -scheme "SWXMLHash OSX" | xcpretty
xcodebuild $ACTION $WORKSPACE -scheme "SWXMLHash iOS" -sdk iphonesimulator -destination "OS=14.3,name=iPhone 12" | xcpretty
xcodebuild $ACTION $WORKSPACE -scheme "SWXMLHash iOS" -sdk iphonesimulator -destination "OS=14.4,name=iPhone 12" | xcpretty
xcodebuild $ACTION $WORKSPACE -scheme "SWXMLHash tvOS" -sdk appletvsimulator -destination "name=Apple TV" | xcpretty
xcodebuild build $WORKSPACE -scheme "SWXMLHash watchOS" -sdk watchsimulator | xcpretty
bash <(curl -s https://codecov.io/bash) -t ${{secrets.CODECOV_TOKEN}}
Expand Down
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.1
5.3
8 changes: 8 additions & 0 deletions SWXMLHash.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
CDDEC7711BF632DD00AB138B /* SWXMLHash.h in Headers */ = {isa = PBXBuildFile; fileRef = CD6083F4196CA106000B4F8D /* SWXMLHash.h */; settings = {ATTRIBUTES = (Public, ); }; };
CDEA72731C00B0D900C10B28 /* SWXMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD60840B196CA11D000B4F8D /* SWXMLHash.swift */; };
CDEA72741C00B0E300C10B28 /* SWXMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD60840B196CA11D000B4F8D /* SWXMLHash.swift */; };
E25A0776261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25A0775261CF79300C68B90 /* XMLParsingValidationTests.swift */; };
E25A0777261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25A0775261CF79300C68B90 /* XMLParsingValidationTests.swift */; };
E25A0778261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25A0775261CF79300C68B90 /* XMLParsingValidationTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -150,6 +153,7 @@
CDDEC74C1BF6311A00AB138B /* SWXMLHash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWXMLHash.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CDDEC7551BF6311B00AB138B /* SWXMLHash tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SWXMLHash tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
CDDEC7681BF6316C00AB138B /* SWXMLHash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWXMLHash.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E25A0775261CF79300C68B90 /* XMLParsingValidationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParsingValidationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -280,6 +284,7 @@
CDC6D13E1D32D98400570DE5 /* TypeConversionPrimitypeTypesTests.swift */,
CDC6D11E1D32D70800570DE5 /* WhiteSpaceParsingTests.swift */,
CDC6D11A1D32D6CE00570DE5 /* XMLParsingTests.swift */,
E25A0775261CF79300C68B90 /* XMLParsingValidationTests.swift */,
);
name = Tests;
path = Tests/SWXMLHashTests;
Expand Down Expand Up @@ -616,6 +621,7 @@
CDC6D12F1D32D79F00570DE5 /* SWXMLHashConfigTests.swift in Sources */,
6C42BED1205183A100137D31 /* shim.swift in Sources */,
CDC6D13F1D32D98400570DE5 /* TypeConversionPrimitypeTypesTests.swift in Sources */,
E25A0776261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */,
CDC6D1431D32D9D200570DE5 /* TypeConversionArrayOfNonPrimitiveTypesTests.swift in Sources */,
CDC6D11F1D32D70800570DE5 /* WhiteSpaceParsingTests.swift in Sources */,
CDC6D1231D32D73900570DE5 /* MixedTextWithXMLElementsTests.swift in Sources */,
Expand Down Expand Up @@ -645,6 +651,7 @@
CDC6D1301D32D79F00570DE5 /* SWXMLHashConfigTests.swift in Sources */,
6C42BED2205183A100137D31 /* shim.swift in Sources */,
CDC6D1401D32D98400570DE5 /* TypeConversionPrimitypeTypesTests.swift in Sources */,
E25A0777261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */,
CDC6D1441D32D9D200570DE5 /* TypeConversionArrayOfNonPrimitiveTypesTests.swift in Sources */,
CDC6D1201D32D70800570DE5 /* WhiteSpaceParsingTests.swift in Sources */,
CDC6D1241D32D73900570DE5 /* MixedTextWithXMLElementsTests.swift in Sources */,
Expand Down Expand Up @@ -674,6 +681,7 @@
CDC6D1311D32D79F00570DE5 /* SWXMLHashConfigTests.swift in Sources */,
6C42BED3205183A100137D31 /* shim.swift in Sources */,
CDC6D1411D32D98400570DE5 /* TypeConversionPrimitypeTypesTests.swift in Sources */,
E25A0778261CF79300C68B90 /* XMLParsingValidationTests.swift in Sources */,
CDC6D1451D32D9D200570DE5 /* TypeConversionArrayOfNonPrimitiveTypesTests.swift in Sources */,
CDC6D1211D32D70800570DE5 /* WhiteSpaceParsingTests.swift in Sources */,
CDC6D1251D32D73900570DE5 /* MixedTextWithXMLElementsTests.swift in Sources */,
Expand Down
130 changes: 112 additions & 18 deletions Source/XMLIndexer+XMLIndexerDeserializable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import Foundation
public protocol XMLIndexerDeserializable {
/// Method for deserializing elements from XMLIndexer
static func deserialize(_ element: XMLIndexer) throws -> Self
/// Method for validating elements post deserialization
func validate() throws
}

/// Provides XMLIndexer deserialization / type transformation support
Expand All @@ -50,6 +52,12 @@ public extension XMLIndexerDeserializable {
throw XMLDeserializationError.implementationIsMissing(
method: "XMLIndexerDeserializable.deserialize(element: XMLIndexer)")
}

/**
A default do nothing implementation of validation.
- throws: nothing
*/
func validate() throws {}
}

// MARK: - XMLElementDeserializable
Expand All @@ -58,6 +66,8 @@ public extension XMLIndexerDeserializable {
public protocol XMLElementDeserializable {
/// Method for deserializing elements from XMLElement
static func deserialize(_ element: XMLElement) throws -> Self
/// Method for validating elements from XMLElement post deserialization
func validate() throws
}

/// Provides XMLElement deserialization / type transformation support
Expand All @@ -74,13 +84,22 @@ public extension XMLElementDeserializable {
throw XMLDeserializationError.implementationIsMissing(
method: "XMLElementDeserializable.deserialize(element: XMLElement)")
}

/**
A default do nothing implementation of validation.
- throws: nothing
*/
func validate() throws {}
}

// MARK: - XMLAttributeDeserializable

/// Provides XMLAttribute deserialization / type transformation support
public protocol XMLAttributeDeserializable {
/// Method for deserializing elements from XMLAttribute
static func deserialize(_ attribute: XMLAttribute) throws -> Self
/// Method for validating elements from XMLAttribute post deserialization
func validate() throws
}

/// Provides XMLAttribute deserialization / type transformation support
Expand All @@ -97,6 +116,11 @@ public extension XMLAttributeDeserializable {
throw XMLDeserializationError.implementationIsMissing(
method: "XMLAttributeDeserializable(element: XMLAttribute)")
}
/**
A default do nothing implementation of validation.
- throws: nothing
*/
func validate() throws {}
}

// MARK: - XMLIndexer Extensions
Expand Down Expand Up @@ -215,7 +239,9 @@ public extension XMLIndexer {
func value<T: XMLElementDeserializable>() throws -> T {
switch self {
case .element(let element):
return try T.deserialize(element)
let deserialized = try T.deserialize(element)
try deserialized.validate()
return deserialized
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -232,7 +258,9 @@ public extension XMLIndexer {
func value<T: XMLElementDeserializable>() throws -> T? {
switch self {
case .element(let element):
return try T.deserialize(element)
let deserialized = try T.deserialize(element)
try deserialized.validate()
return deserialized
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -249,9 +277,17 @@ public extension XMLIndexer {
func value<T: XMLElementDeserializable>() throws -> [T] {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize($0) }
return try elements.map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize($0) }
return try [element].map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -268,9 +304,17 @@ public extension XMLIndexer {
func value<T: XMLElementDeserializable>() throws -> [T]? {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize($0) }
return try elements.map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize($0) }
return try [element].map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -287,9 +331,17 @@ public extension XMLIndexer {
func value<T: XMLElementDeserializable>() throws -> [T?] {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize($0) }
return try elements.map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize($0) }
return try [element].map {
let deserialized = try T.deserialize($0)
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -308,7 +360,9 @@ public extension XMLIndexer {
func value<T: XMLIndexerDeserializable>() throws -> T {
switch self {
case .element:
return try T.deserialize(self)
let deserialized = try T.deserialize(self)
try deserialized.validate()
return deserialized
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -325,7 +379,9 @@ public extension XMLIndexer {
func value<T: XMLIndexerDeserializable>() throws -> T? {
switch self {
case .element:
return try T.deserialize(self)
let deserialized = try T.deserialize(self)
try deserialized.validate()
return deserialized
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -342,9 +398,17 @@ public extension XMLIndexer {
func value<T>() throws -> [T] where T: XMLIndexerDeserializable {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize( XMLIndexer($0) ) }
return try elements.map {
let deserialized = try T.deserialize( XMLIndexer($0) )
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
return try [element].map {
let deserialized = try T.deserialize( XMLIndexer($0) )
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -361,9 +425,17 @@ public extension XMLIndexer {
func value<T: XMLIndexerDeserializable>() throws -> [T]? {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize( XMLIndexer($0) ) }
return try elements.map {
let deserialized = try T.deserialize(XMLIndexer($0))
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
return try [element].map {
let deserialized = try T.deserialize(XMLIndexer($0))
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand All @@ -380,9 +452,17 @@ public extension XMLIndexer {
func value<T: XMLIndexerDeserializable>() throws -> [T?] {
switch self {
case .list(let elements):
return try elements.map { try T.deserialize( XMLIndexer($0) ) }
return try elements.map {
let deserialized = try T.deserialize(XMLIndexer($0))
try deserialized.validate()
return deserialized
}
case .element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
return try [element].map {
let deserialized = try T.deserialize(XMLIndexer($0))
try deserialized.validate()
return deserialized
}
case .stream(let opStream):
return try opStream.findElements().value()
default:
Expand Down Expand Up @@ -483,7 +563,9 @@ extension XMLElement {
*/
public func value<T: XMLAttributeDeserializable>(ofAttribute attr: String) throws -> T {
if let attr = self.attribute(by: attr) {
return try T.deserialize(attr)
let deserialized = try T.deserialize(attr)
try deserialized.validate()
return deserialized
} else {
throw XMLDeserializationError.attributeDoesNotExist(element: self, attribute: attr)
}
Expand All @@ -497,7 +579,9 @@ extension XMLElement {
*/
public func value<T: XMLAttributeDeserializable>(ofAttribute attr: String) -> T? {
if let attr = self.attribute(by: attr) {
return try? T.deserialize(attr)
let deserialized = try? T.deserialize(attr)
if deserialized != nil { try? deserialized?.validate() }
return deserialized
} else {
return nil
}
Expand Down Expand Up @@ -631,6 +715,8 @@ extension String: XMLElementDeserializable, XMLAttributeDeserializable {
public static func deserialize(_ attribute: XMLAttribute) -> String {
attribute.text
}

public func validate() {}
}

extension Int: XMLElementDeserializable, XMLAttributeDeserializable {
Expand Down Expand Up @@ -664,6 +750,8 @@ extension Int: XMLElementDeserializable, XMLAttributeDeserializable {
}
return value
}

public func validate() {}
}

extension Double: XMLElementDeserializable, XMLAttributeDeserializable {
Expand Down Expand Up @@ -697,6 +785,8 @@ extension Double: XMLElementDeserializable, XMLAttributeDeserializable {
}
return value
}

public func validate() {}
}

extension Float: XMLElementDeserializable, XMLAttributeDeserializable {
Expand Down Expand Up @@ -730,6 +820,8 @@ extension Float: XMLElementDeserializable, XMLAttributeDeserializable {
}
return value
}

public func validate() {}
}

extension Bool: XMLElementDeserializable, XMLAttributeDeserializable {
Expand Down Expand Up @@ -762,4 +854,6 @@ extension Bool: XMLElementDeserializable, XMLAttributeDeserializable {
return value
}
// swiftlint:enable line_length

public func validate() {}
}
4 changes: 2 additions & 2 deletions Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}

// swiftlint:disable nesting
func testShouldConvertAttributeToNonOptionalWithStringRawRepresentable() {
enum Keys: String {
case string
Expand Down Expand Up @@ -201,7 +200,6 @@ class TypeConversionBasicTypesTests: XCTestCase {
let value: String? = parser!["root"]["attr"].value(ofAttribute: Keys.missing)
XCTAssertNil(value)
}
// swiftlint:enable nesting

func testIntShouldConvertValueToNonOptional() {
do {
Expand Down Expand Up @@ -616,6 +614,8 @@ class TypeConversionBasicTypesTests: XCTestCase {
}
}

enum BasicItemValidation: Error { case priceOutOfBounds(Double) }

struct BasicItem: XMLIndexerDeserializable {
let name: String
let price: Double
Expand Down
Loading