Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #22 from Sage-Bionetworks/fix-inheritance
Browse files Browse the repository at this point in the history
Fixes for building the Json Schema documentation with interface inheritance
  • Loading branch information
syoung-smallwisdom authored Jul 7, 2022
2 parents 8ac16d7 + c947458 commit 35fb922
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 3 deletions.
7 changes: 5 additions & 2 deletions Sources/JsonModel/Documentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,10 @@ public class JsonDocumentBuilder {
}

public func buildSchemas() throws -> [JsonSchema] {
let roots = self.objects.filter { $0.isRoot }
// Only include roots that have a shared base url
let roots = self.objects.filter {
$0.isRoot && $0.refId.baseURL == self.baseUrl
}
return try roots.map { (rootPointer) -> JsonSchema in
guard let docType = rootPointer.klass as? DocumentableBase.Type else {
throw DocumentableError.invalidMapping("\(rootPointer.klass) does not conform to `DocumentableBase`.")
Expand Down Expand Up @@ -597,7 +600,7 @@ public class JsonDocumentBuilder {
fileprivate func buildProperties(for dType: DocumentableBase.Type, in objPointer: KlassPointer) throws
-> (properties: [String : JsonSchemaProperty], required: [String]) {

let parentDocType = objPointer.mainParent?.klass.documentableType() as? DocumentableBase.Type
let parentDocType = objPointer.mainParent?.klass.documentableType() as? DocumentableInterface.Type
let parentKeys = parentDocType?.codingKeys() ?? []

let codingKeys = dType.codingKeys()
Expand Down
2 changes: 1 addition & 1 deletion Sources/JsonModel/JsonSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ public struct JsonSchemaObject : Codable, Hashable {
self.additionalProperties = additionalProperties
self.allOf = (interfaces?.count ?? 0) == 0 ? nil : interfaces
self.orderedProperties = (properties?.count ?? 0) == 0 ? nil : .init(properties!, orderedKeys: codingKeys)
self.required = required
self.required = (required?.count ?? 0) == 0 ? nil : required
self.examples = (examples?.count ?? 0) == 0 ? nil : examples!.map {
AnyCodableDictionary($0, orderedKeys: codingKeys)
}
Expand Down
237 changes: 237 additions & 0 deletions Tests/JsonModelTests/DocumentableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,84 @@ final class DocumentableTests: XCTestCase {
XCTFail("Missing definition mapping for \(key)")
}
}

func testResultDataFactory() {
do {
let factory = TestResultFactoryA()
let doc = JsonDocumentBuilder(factory: factory)
let schemas = try doc.buildSchemas()

XCTAssertEqual(schemas.count, 4)

if let testSchema = schemas.first(where: { $0.id.className == "TestResult"}),
let sampleDef = testSchema.definitions?["Sample"],
case .object(let sampleObj) = sampleDef, let sampleProps = sampleObj.properties {
XCTAssertNotNil(sampleProps["index"])
XCTAssertNotNil(sampleProps["identifier"])
XCTAssertNotNil(sampleProps["startDate"])
}
else {
XCTFail("Failed to build schema.")
}

if let rootSchema = schemas.first(where: { $0.id.className == "ResultData"}),
let def = rootSchema.definitions?["ResultObject"],
case .object(let obj) = def, let props = obj.properties {
XCTAssertNotNil(props["type"])
XCTAssertNil(props["identifier"])
XCTAssertNil(props["startDate"])
XCTAssertNil(props["endDate"])
}
else {
XCTFail("Failed to build schema.")
}
}
catch let err {
XCTFail("Failed to build the JsonSchema: \(err)")
}
}

func testResultDataFactoryExternal() {
do {
let factory = TestResultFactoryB()
let doc = JsonDocumentBuilder(factory: factory)
let schemas = try doc.buildSchemas()

let schemaNames = schemas.map { $0.id.className }
XCTAssertEqual(["TestResultExternal"], schemaNames)

guard let schema = schemas.first, schema.id.className == "TestResultExternal" else {
XCTFail("Failed to build and filter schemas")
return
}

if let interface = schema.root.allOf?.first?.refId {
XCTAssertEqual("ResultData", interface.className)
XCTAssertTrue(interface.isExternal)
XCTAssertEqual("https://sage-bionetworks.github.io/mobile-client-json/schemas/v2/ResultData.json", interface.classPath)
}
else {
XCTFail("Failed to add expected interfaces.")
}

if let props = schema.root.properties,
let typeProp = props["type"],
case .const(let constType) = typeProp {
XCTAssertEqual("test", constType.const)
XCTAssertEqual("https://sage-bionetworks.github.io/mobile-client-json/schemas/v2/ResultData.json#SerializableResultType", constType.ref?.classPath)
}
else {
XCTFail("Failed to add expected property.")
}
}
catch let err {
XCTFail("Failed to build the JsonSchema: \(err)")
}
}
}

// MARK: Test objects

class AnotherTestFactory : SerializationFactory {
let sampleSerializer = SampleSerializer()
let anotherSerializer = AnotherSerializer()
Expand Down Expand Up @@ -360,3 +436,164 @@ extension AnotherB : DocumentableStruct {
return [AnotherB()]
}
}

class TestResultFactoryA : ResultDataFactory {
required init() {
super.init()
resultSerializer.add(TestResult())
}
}

extension SerializableResultType {
static let test: SerializableResultType = "test"
}

struct TestResult : SerializableResultData, DocumentableStruct, DocumentableRootObject {
private enum CodingKeys : String, OrderedEnumCodingKey {
case serializableType="type", identifier, startDate, endDate, sample
}

var serializableType: SerializableResultType = .test
var identifier: String = "test"
var startDate: Date = Date()
var endDate: Date = Date()
var sample: Sample = .init()

func deepCopy() -> TestResult {
self
}

public var jsonSchema: URL {
URL(string: "\(self.className).json", relativeTo: kSageJsonSchemaBaseURL)!
}

public var documentDescription: String? {
"A result used to test root serialization."
}

static func examples() -> [TestResult] {
[.init()]
}

static func codingKeys() -> [CodingKey] {
CodingKeys.allCases
}

static func isRequired(_ codingKey: CodingKey) -> Bool {
true
}

static func documentProperty(for codingKey: CodingKey) throws -> DocumentProperty {
guard let key = codingKey as? CodingKeys else {
throw DocumentableError.invalidCodingKey(codingKey, "\(codingKey) is not recognized for this class")
}
switch key {
case .serializableType:
return .init(constValue: SerializableResultType.test)
case .identifier:
return .init(propertyType: .primitive(.string))
case .startDate, .endDate:
return .init(propertyType: .format(.dateTime))
case .sample:
return .init(propertyType: .reference(Sample.documentableType()))
}
}

struct Sample : DocumentableStruct {
private enum CodingKeys : String, OrderedEnumCodingKey {
case index, identifier, startDate
}

var index: Int = 0
var identifier: String = "sample"
var startDate: Date = Date()

static func examples() -> [Sample] {
[.init()]
}

static func codingKeys() -> [CodingKey] {
CodingKeys.allCases
}

static func isRequired(_ codingKey: CodingKey) -> Bool {
true
}

static func documentProperty(for codingKey: CodingKey) throws -> DocumentProperty {
guard let key = codingKey as? CodingKeys else {
throw DocumentableError.invalidCodingKey(codingKey, "\(codingKey) is not recognized for this class")
}
switch key {
case .index:
return .init(propertyType: .primitive(.integer))
case .identifier:
return .init(propertyType: .primitive(.string))
case .startDate:
return .init(propertyType: .format(.dateTime))
}
}
}
}

let testBURL = URL(string: "https://foo.org/schemas/")!

class TestResultFactoryB : ResultDataFactory {
required init() {
super.init()
resultSerializer.add(TestResultExternal())
}

override var jsonSchemaBaseURL: URL {
testBURL
}
}

struct TestResultExternal : SerializableResultData, DocumentableStruct, DocumentableRootObject {
private enum CodingKeys : String, OrderedEnumCodingKey {
case serializableType="type", identifier, startDate, endDate
}

var serializableType: SerializableResultType = .test
var identifier: String = "test"
var startDate: Date = Date()
var endDate: Date = Date()

func deepCopy() -> TestResultExternal {
self
}

public var jsonSchema: URL {
URL(string: "\(self.className).json", relativeTo: testBURL)!
}

public var documentDescription: String? {
"A result used to test root serialization."
}

static func examples() -> [TestResultExternal] {
[.init()]
}

static func codingKeys() -> [CodingKey] {
CodingKeys.allCases
}

static func isRequired(_ codingKey: CodingKey) -> Bool {
true
}

static func documentProperty(for codingKey: CodingKey) throws -> DocumentProperty {
guard let key = codingKey as? CodingKeys else {
throw DocumentableError.invalidCodingKey(codingKey, "\(codingKey) is not recognized for this class")
}
switch key {
case .serializableType:
return .init(constValue: SerializableResultType.test)
case .identifier:
return .init(propertyType: .primitive(.string))
case .startDate, .endDate:
return .init(propertyType: .format(.dateTime))
}
}
}

0 comments on commit 35fb922

Please sign in to comment.