I have a lot of DTOs in my app which log some field. That field should not be logged because the data is kind of sensitive. The model looks like this:
typealias HiddenFieldType = String
struct DTO1 {
var field1_1: String
var fieldToHide: HiddenFieldType
var field1_2: String
var field1_3: String
}
struct DTO2 {
var field2_1: String
var field2_2: String
var fieldToHide: HiddenFieldType
var field2_3: String
}
the code which outputs the data is like this (actually it's os_log
in a real app):
func test() {
let dto1 = DTO1(field1_1: "1_1", fieldToHide: "super-secret-1", field1_2: "1_2", field1_3: "1_3")
let dto2 = DTO2(field2_1: "2_1", field2_2: "2_2", fieldToHide: "super-secret-2", field2_3: "2_3")
print("Test1: dto1=\(dto1) dto2=\(dto2)")
}
It seems the field can be hidden in DTO1 with such code:
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: DTO1) {
appendInterpolation("field1_1: \(value.field1_1), fieldToHide: 🤷♀️, field1_2: \(value.field1_2), field1_3: \(value.field1_3)")
}
}
However, the solution is neither scalable nor maintainable:
appendInterpolation
- a lot of boilerplateappendInterpolation
etcI tried to add interpolation for HiddenFieldType
(assuming it's a type, just like DTO1
...):
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: HiddenFieldType) {
appendInterpolation("🤷♀️")
}
}
But this solution doesn't work at all:
appendInterpolation
to appendLiteral
, there's no recursion, but "super-secret-1" is not hiddenI tried overriding DefaultStringInterpolation
, conforming to ExpressibleByStringLiteral
/ExpressibleByStringInterpolation
, but it doesn't work: the compiler says that HiddenFieldType
is String
, and Conformance of 'String' to protocol 'ExpressibleByStringLiteral' was already stated in the type's module 'Swift'
The only approach I can imagine is changing typealias HiddenFieldType = String
to struct HiddenFieldType { let value: String }
, so the HiddenFieldType
becomes a "real" type.
Then such code doesn't cause an infinite recursion anymore, but doesn't works either (the value is unhidden)
struct HiddenFieldType {
let value: String
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: HiddenFieldType) {
appendInterpolation("🤷♀️")
}
}
This code finally works:
struct HiddenFieldType {
let value: String
}
extension HiddenFieldType: CustomStringConvertible {
var description: String {
"🤷♀️"
}
}
As I can't imagine any better, for now I'd use this approach, but it also has some slight scalability issues, as I must update each DTO
's initializing point:
from
let dto1 = DTO1(field1_1: "1_1", fieldToHide: "super-secret-1", field1_2: "1_2", field1_3: "1_3")
to
let dto1 = DTO1(field1_1: "1_1", fieldToHide: .init(value: "super-secret-1"), field1_2: "1_2", field1_3: "1_3")
and I hoped to only add some extension in the file which contains typealias HiddenFieldType = String
, and not to update the entire code.
HiddenFieldType
without changing it from typealias
to struct
, and without updating each DTO
?Thanks in advance
Is it possible to hide the value of HiddenFieldType without changing it from typealias to struct
I think you're attempting to use the wrong tool for the job here. A typealias
is just a name change, and it sounds like you want something that acts fundamentally different than a String
(i.e. one gets printed when passed into an os_log
call and one doesn't). You won't be able to write logic that treats a String
different from its typealias
; the compiler doesn't differentiate between them.
Is it possible to make your DTO
s classes instead of structs? (EDIT: see below, you can keep them as structs and just use a protocol extension) If so, you could use reflection on a superclass to accomplish this without having to manually specify the description
for every different DTO.
struct HiddenFieldType {
let value: String
}
open class DTO: CustomStringConvertible {
public var description: String {
Mirror(reflecting: self).children.compactMap { $0.value as? String }.joined(separator: "\n")
}
}
final class DTO1: DTO {
let field1_1: String
let field1_2: String
let fieldToHide: HiddenFieldType
init(field1_1: String, field1_2: String, fieldToHide: HiddenFieldType) {
self. field1_1 = field1_1
self. field1_2 = field1_2
self. fieldToHide = fieldToHide
}
}
Note that I'm including all Strings in the description
but, if you have types other than String
and HiddenFieldType
that you want to log, you could always just filter
out the HiddenFieldType
s specifically.
Personally, I'd be hesitant to rely on reflection for any critical code but other people have more tolerance for it so its a judgement call.
EDIT:
You don't need to use inheritance to accomplish this. Instead of a superclass, DTO
should be a protocol that conforms to CustomStringConvertible
:
protocol DTO: CustomStringConvertible {}
extension DTO {
public var description: String {
Mirror(reflecting: self).children.compactMap { $0.value as? String }.joined(separator: "\n")
}
}