Fix default encoders for top-level values

This commit is contained in:
John Estropia
2020-01-21 17:03:12 +09:00
parent 92ad895044
commit e9c3312612
7 changed files with 256 additions and 83 deletions

View File

@@ -114,7 +114,8 @@ class Person: CoreStoreObject {
var customField: CustomType var customField: CustomType
@Field.Coded( @Field.Coded(
"job", coder: ( "job",
coder: (
encode: { $0.toData() }, encode: { $0.toData() },
decode: { $0.flatMap(Job.init(data:)) ?? .unemployed } decode: { $0.flatMap(Job.init(data:)) ?? .unemployed }
) )

View File

@@ -68,89 +68,124 @@ extension DynamicSchema {
for (attributeName, attribute) in attributesByName { for (attributeName, attribute) in attributesByName {
let containerType: String let containerType: String
if attribute.attributeType == .transformableAttributeType { if attribute.isTransient || attribute.attributeType == .undefinedAttributeType {
if attribute.isOptional { containerType = "Field.Computed"
}
containerType = "Transformable.Optional" else if attribute.attributeType == .transformableAttributeType {
}
else { containerType = "Field.Coded"
containerType = "Transformable.Required"
}
} }
else { else {
if attribute.isOptional { containerType = "Field.Stored"
containerType = "Value.Optional"
}
else {
containerType = "Value.Required"
}
} }
let valueType: Any.Type var valueTypeString: String
var defaultString = "" var defaultString = ""
var coderString = ""
switch attribute.attributeType { switch attribute.attributeType {
case .integer16AttributeType: case .integer16AttributeType:
valueType = Int16.self valueTypeString = String(describing: Int16.self)
if let defaultValue = (attribute.defaultValue as! Int16.QueryableNativeType?).flatMap(Int16.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Int16.QueryableNativeType?).flatMap(Int16.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue)" defaultString = " = \(defaultValue)"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .integer32AttributeType: case .integer32AttributeType:
valueType = Int32.self valueTypeString = String(describing: Int32.self)
if let defaultValue = (attribute.defaultValue as! Int32.QueryableNativeType?).flatMap(Int32.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Int32.QueryableNativeType?).flatMap(Int32.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue)" defaultString = " = \(defaultValue)"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .integer64AttributeType: case .integer64AttributeType:
valueType = Int64.self valueTypeString = String(describing: Int64.self)
if let defaultValue = (attribute.defaultValue as! Int64.QueryableNativeType?).flatMap(Int64.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Int64.QueryableNativeType?).flatMap(Int64.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue)" defaultString = " = \(defaultValue)"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .decimalAttributeType: case .decimalAttributeType:
valueType = NSDecimalNumber.self valueTypeString = String(describing: NSDecimalNumber.self)
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber?) { if let defaultValue = (attribute.defaultValue as! NSDecimalNumber?) {
defaultString = ", initial: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")" defaultString = " = NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .doubleAttributeType: case .doubleAttributeType:
valueType = Double.self valueTypeString = String(describing: Double.self)
if let defaultValue = (attribute.defaultValue as! Double.QueryableNativeType?).flatMap(Double.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Double.QueryableNativeType?).flatMap(Double.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue)" defaultString = " = \(defaultValue)"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .floatAttributeType: case .floatAttributeType:
valueType = Float.self valueTypeString = String(describing: Float.self)
if let defaultValue = (attribute.defaultValue as! Float.QueryableNativeType?).flatMap(Float.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Float.QueryableNativeType?).flatMap(Float.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue)" defaultString = " = \(defaultValue)"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .stringAttributeType: case .stringAttributeType:
valueType = String.self valueTypeString = String(describing: String.self)
if let defaultValue = (attribute.defaultValue as! String.QueryableNativeType?).flatMap(String.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! String.QueryableNativeType?).flatMap(String.cs_fromQueryableNativeType) {
// TODO: escape strings defaultString = " = \"\(defaultValue.replacingOccurrences(of: "\\", with: "\\\\"))\""
defaultString = ", initial: \"\(defaultValue)\"" }
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .booleanAttributeType: case .booleanAttributeType:
valueType = Bool.self valueTypeString = String(describing: Bool.self)
if let defaultValue = (attribute.defaultValue as! Bool.QueryableNativeType?).flatMap(Bool.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Bool.QueryableNativeType?).flatMap(Bool.cs_fromQueryableNativeType) {
defaultString = ", initial: \(defaultValue ? "true" : "false")" defaultString = " = \(defaultValue ? "true" : "false")"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .dateAttributeType: case .dateAttributeType:
valueType = Date.self valueTypeString = String(describing: Date.self)
if let defaultValue = (attribute.defaultValue as! Date.QueryableNativeType?).flatMap(Date.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Date.QueryableNativeType?).flatMap(Date.cs_fromQueryableNativeType) {
defaultString = ", initial: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))" defaultString = " = Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .binaryDataAttributeType: case .binaryDataAttributeType:
valueType = Data.self valueTypeString = String(describing: Data.self)
if let defaultValue = (attribute.defaultValue as! Data.QueryableNativeType?).flatMap(Data.cs_fromQueryableNativeType) { if let defaultValue = (attribute.defaultValue as! Data.QueryableNativeType?).flatMap(Data.cs_fromQueryableNativeType) {
let bytes = defaultValue.withUnsafeBytes { (pointer) in let bytes = defaultValue.withUnsafeBytes { (pointer) in
@@ -158,49 +193,106 @@ extension DynamicSchema {
.bindMemory(to: UInt64.self) .bindMemory(to: UInt64.self)
.map({ "\("0x\(String($0, radix: 16, uppercase: false))")" }) .map({ "\("0x\(String($0, radix: 16, uppercase: false))")" })
} }
defaultString = ", initial: Data(bytes: [\(bytes.joined(separator: ", "))])" defaultString = " = Data(bytes: [\(bytes.joined(separator: ", "))])"
}
else if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
} }
case .transformableAttributeType: case .transformableAttributeType:
if let valueTransformerName = attribute.valueTransformerName {
coderString = ", coder: /* Required compatible FieldCoderType implementation for ValueTransformer named \"\(valueTransformerName)\" */"
}
else {
coderString = ", coder: FieldCoders.NSCoding.self"
}
if let attributeValueClassName = attribute.attributeValueClassName { if let attributeValueClassName = attribute.attributeValueClassName {
valueType = NSClassFromString(attributeValueClassName)! valueTypeString = String(describing: NSClassFromString(attributeValueClassName)!)
}
else {
valueTypeString = "/* <required> */"
}
if let defaultValue = attribute.defaultValue {
switch defaultValue {
case let defaultValueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
if let defaultValue = defaultValueBox.value {
defaultString = " = /* \"\(defaultValue)\" */"
}
else if attribute.isOptional {
defaultString = " = nil"
}
else {
defaultString = " = /* <required> */"
}
case let defaultValue:
defaultString = " = /* \"\(defaultValue)\" */"
}
}
else if attribute.isOptional {
defaultString = " = nil"
} }
else { else {
valueType = (NSCoding & NSCopying).self defaultString = " = /* <required> */"
} }
if let defaultValue = attribute.defaultValue { if attribute.isOptional {
defaultString = ", initial: /* \"\(defaultValue)\" */" valueTypeString += "?"
} }
else if !attribute.isOptional {
case .undefinedAttributeType where attribute.isTransient:
defaultString = ", initial: /* required */" coderString = ", customGetter: \\* <required> *\\"
if let attributeValueClassName = attribute.attributeValueClassName {
valueTypeString = String(describing: NSClassFromString(attributeValueClassName)!)
} }
case .undefinedAttributeType: else {
#warning("TODO: Field.Computed")
continue valueTypeString = " = /* <required> */"
}
if attribute.isOptional {
valueTypeString += "?"
defaultString = " = nil"
}
default: default:
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)") fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
} }
let transientString = attribute.isTransient ? ", isTransient: true" : ""
// TODO: escape strings
let versionHashModifierString = attribute.versionHashModifier let versionHashModifierString = attribute.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" .map({ ", versionHashModifier: \"\($0)\"" }) ?? ""
// TODO: escape strings
let renamingIdentifierString = attribute.renamingIdentifier let renamingIdentifierString = attribute.renamingIdentifier
.flatMap({ ($0 == attributeName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? "" .map({ ($0 == attributeName ? "" : ", previousVersionKeyPath: \"\($0)\"") }) ?? ""
output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n") if attributeName.hasPrefix("_") {
output.append(" #warning(\"Field variable names cannot start with underscores)")
}
output.append(" @\(containerType)(\"\(attributeName)\"\(versionHashModifierString)\(renamingIdentifierString)\(coderString))\n")
output.append(" var \(attributeName): \(valueTypeString)\(defaultString)\n\n")
} }
} }
let relationshipsByName = entity.relationshipsByName let relationshipsByName = entity.relationshipsByName
if !relationshipsByName.isEmpty { if !relationshipsByName.isEmpty {
output.append(" \n") output.append(" \n")
for (relationshipName, relationship) in relationshipsByName { for (relationshipName, relationship) in relationshipsByName {
let containerType: String let containerType: String
let destinationEntityName = relationship.destinationEntity!.name!
var minCountString = "" var minCountString = ""
var maxCountString = "" var maxCountString = ""
if relationship.isToMany { if relationship.isToMany {
@@ -209,11 +301,11 @@ extension DynamicSchema {
let maxCount = relationship.maxCount let maxCount = relationship.maxCount
if relationship.isOrdered { if relationship.isOrdered {
containerType = "Relationship.ToManyOrdered" containerType = "[\(destinationEntityName)]"
} }
else { else {
containerType = "Relationship.ToManyUnordered" containerType = "Set<\(destinationEntityName)>"
} }
if minCount > 0 { if minCount > 0 {
@@ -225,16 +317,16 @@ extension DynamicSchema {
} }
} }
else { else {
containerType = "Relationship.ToOne" containerType = "\(destinationEntityName)?"
} }
var inverseString = "" var inverseString = ""
let relationshipQualifier = "\(entityName).\(relationshipName)" let relationshipQualifier = "\(entityName).\(relationshipName)"
if !addedInverse.contains(relationshipQualifier), if !addedInverse.contains(relationshipQualifier),
let inverseRelationship = relationship.inverseRelationship { let inverseRelationship = relationship.inverseRelationship {
inverseString = ", inverse: { $0.\(inverseRelationship.name) }" inverseString = ", inverse: \\.$\(inverseRelationship.name)"
addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)") addedInverse.insert("\(destinationEntityName).\(inverseRelationship.name)")
} }
var deleteRuleString = "" var deleteRuleString = ""
if relationship.deleteRule != .nullifyDeleteRule { if relationship.deleteRule != .nullifyDeleteRule {
@@ -255,10 +347,15 @@ extension DynamicSchema {
} }
} }
let versionHashModifierString = relationship.versionHashModifier let versionHashModifierString = relationship.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" .map({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = relationship.renamingIdentifier let renamingIdentifierString = relationship.renamingIdentifier
.flatMap({ ($0 == relationshipName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? "" .map({ ($0 == relationshipName ? "" : ", previousVersionKeyPath: \"\($0)\"") }) ?? ""
output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n") if relationshipName.hasPrefix("_") {
output.append(" #error(\"Field variable names cannot start with underscores)\n")
}
output.append(" @Field.Relationship(\"\(relationshipName)\"\(minCountString)\(maxCountString)\(inverseString)\(deleteRuleString)\(versionHashModifierString)\(renamingIdentifierString))\n")
output.append(" var \(relationshipName): \(containerType)\n\n")
} }
} }
} }

View File

@@ -37,7 +37,40 @@ extension FieldContainer {
// @dynamicMemberLookup // @dynamicMemberLookup
public struct Relationship<V: FieldRelationshipType>: RelationshipKeyPathStringConvertible, FieldRelationshipProtocol { public struct Relationship<V: FieldRelationshipType>: RelationshipKeyPathStringConvertible, FieldRelationshipProtocol {
public typealias DeleteRule = RelationshipContainer<O>.DeleteRule /**
Overload for compiler error message only
*/
@available(*, unavailable, message: "Field.Relationship properties are not allowed to have initial values, including `nil`.")
public init(
wrappedValue initial: @autoclosure @escaping () -> V,
_ keyPath: KeyPathString,
minCount: Int = 0,
maxCount: Int = 0,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil
) {
fatalError()
}
/**
Overload for compiler error message only
*/
@available(*, unavailable, message: "Field.Relationship properties are not allowed to have initial values, including `nil`.")
public init<D>(
wrappedValue initial: @autoclosure @escaping () -> V,
_ keyPath: KeyPathString,
minCount: Int = 0,
maxCount: Int = 0,
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil
) {
fatalError()
}
// MARK: @propertyWrapper // MARK: @propertyWrapper
@@ -188,6 +221,45 @@ extension FieldContainer {
) )
} }
} }
// MARK: - DeleteRule
/**
These constants define what happens to relationships when an object is deleted.
*/
public enum DeleteRule {
// MARK: Public
/**
If the object is deleted, back pointers from the objects to which it is related are nullified.
*/
case nullify
/**
If the object is deleted, the destination object or objects of this relationship are also deleted.
*/
case cascade
/**
If the destination of this relationship is not nil, the delete creates a validation error.
*/
case deny
// MARK: Internal
internal var nativeValue: NSDeleteRule {
switch self {
case .nullify: return .nullifyDeleteRule
case .cascade: return .cascadeDeleteRule
case .deny: return .denyDeleteRule
}
}
}
} }
} }
@@ -271,7 +343,7 @@ extension FieldContainer.Relationship where V: FieldRelationshipToManyOrderedTyp
_ keyPath: KeyPathString, _ keyPath: KeyPathString,
minCount: Int = 0, minCount: Int = 0,
maxCount: Int = 0, maxCount: Int = 0,
inverse: @escaping (V.DestinationObjectType) -> FieldContainer<V.DestinationObjectType>.Relationship<D>, inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
deleteRule: DeleteRule = .nullify, deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil, versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
@@ -283,7 +355,7 @@ extension FieldContainer.Relationship where V: FieldRelationshipToManyOrderedTyp
isToMany: true, isToMany: true,
isOrdered: true, isOrdered: true,
deleteRule: deleteRule, deleteRule: deleteRule,
inverseKeyPath: { inverse(V.DestinationObjectType.meta).keyPath }, inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath },
versionHashModifier: versionHashModifier, versionHashModifier: versionHashModifier,
renamingIdentifier: previousVersionKeyPath, renamingIdentifier: previousVersionKeyPath,
affectedByKeyPaths: affectedByKeyPaths, affectedByKeyPaths: affectedByKeyPaths,
@@ -323,7 +395,7 @@ extension FieldContainer.Relationship where V: FieldRelationshipToManyUnorderedT
_ keyPath: KeyPathString, _ keyPath: KeyPathString,
minCount: Int = 0, minCount: Int = 0,
maxCount: Int = 0, maxCount: Int = 0,
inverse: @escaping (V.DestinationObjectType) -> FieldContainer<V.DestinationObjectType>.Relationship<D>, inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
deleteRule: DeleteRule = .nullify, deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil, versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
@@ -335,7 +407,7 @@ extension FieldContainer.Relationship where V: FieldRelationshipToManyUnorderedT
isToMany: true, isToMany: true,
isOrdered: false, isOrdered: false,
deleteRule: deleteRule, deleteRule: deleteRule,
inverseKeyPath: { inverse(V.DestinationObjectType.meta).keyPath }, inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath },
versionHashModifier: versionHashModifier, versionHashModifier: versionHashModifier,
renamingIdentifier: previousVersionKeyPath, renamingIdentifier: previousVersionKeyPath,
affectedByKeyPaths: affectedByKeyPaths, affectedByKeyPaths: affectedByKeyPaths,

View File

@@ -44,7 +44,7 @@ extension FieldCoders {
return nil return nil
} }
return try? JSONEncoder().encode(fieldValue) return try! JSONEncoder().encode([fieldValue])
} }
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
@@ -53,7 +53,7 @@ extension FieldCoders {
return nil return nil
} }
return try? JSONDecoder().decode(FieldStoredValue.self, from: data) return try! JSONDecoder().decode([FieldStoredValue].self, from: data).first
} }
} }
} }

View File

@@ -32,7 +32,7 @@ extension FieldCoders {
// MARK: - NSCoding // MARK: - NSCoding
public struct NSCoding<V: NSObject & Foundation.NSCoding>: FieldCoderType { public struct NSCoding<V: Foundation.NSObject & Foundation.NSCoding>: FieldCoderType {
// MARK: FieldCoderType // MARK: FieldCoderType

View File

@@ -44,7 +44,7 @@ extension FieldCoders {
return nil return nil
} }
return try? PropertyListEncoder().encode(fieldValue) return try! PropertyListEncoder().encode([fieldValue])
} }
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
@@ -53,7 +53,7 @@ extension FieldCoders {
return nil return nil
} }
return try? PropertyListDecoder().decode(FieldStoredValue.self, from: data) return try! PropertyListDecoder().decode([FieldStoredValue].self, from: data).first
} }
} }
} }

View File

@@ -63,7 +63,10 @@ extension DynamicObject where Self: CoreStoreObject {
public enum RelationshipContainer<O: CoreStoreObject> { public enum RelationshipContainer<O: CoreStoreObject> {
// MARK: - DeleteRule // MARK: - DeleteRule
/**
These constants define what happens to relationships when an object is deleted.
*/
public enum DeleteRule { public enum DeleteRule {
// MARK: Public // MARK: Public