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)")
}
Approach #1
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:
- the same extension should be added for each DTO
- each field should be included into
appendInterpolation
- a lot of boilerplate - if a new field is added to some DTO, we may forget to update
appendInterpolation
etc
Approach #2
I 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:
- the compiler says "Function call causes an infinite recursion"
- and it actually causes an infinite recursion
- when changing
appendInterpolation
toappendLiteral
, there's no recursion, but "super-secret-1" is not hidden
Approach #3
I 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.
Approach #4
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("♀️")
}
}
Approach #5
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.
The questions
- Is it possible to hide the value of
HiddenFieldType
without changing it fromtypealias
tostruct
, and without updating eachDTO
? - Is there any better approach than 5?
Thanks in advance
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 aString
(i.e. one gets printed when passed into anos_log
call and one doesn't). You won't be able to write logic that treats aString
different from itstypealias
; 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 thedescription
for every different DTO.Note that I'm including all Strings in the
description
but, if you have types other thanString
andHiddenFieldType
that you want to log, you could always justfilter
out theHiddenFieldType
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 toCustomStringConvertible
: