1 /*******************************************************************************
2  * User-Defined Attributes used to tag fields as dproto-serializable
3  *
4  * Authors: Matthew Soucy, dproto@msoucy.me
5  */
6 module dproto.attributes;
7 
8 import dproto.serialize;
9 import painlesstraits : getAnnotation, hasValueAnnotation;
10 import dproto.compat;
11 
12 import std.traits : isArray;
13 import std.typecons : Nullable;
14 
15 alias Identity(alias A) = A;
16 
17 struct ProtoField
18 {
19 	string wireType;
20 	ubyte fieldNumber;
21 	@disable this();
22 	this(string w, ubyte f) {
23 		wireType = w;
24 		fieldNumber = f;
25 	}
26 	@nogc auto header() {
27 		return (wireType.msgType | (fieldNumber << 3));
28 	}
29 }
30 
31 struct Required {}
32 struct Packed {}
33 
34 template TagId(alias T)
35 	if(hasValueAnnotation!(T, ProtoField))
36 {
37 	enum TagId = getAnnotation!(T, ProtoField).fieldNumber;
38 }
39 
40 template ProtoAccessors()
41 {
42 
43 	static auto fromProto(R)(auto ref R data)
44 		if(isProtoInputRange!R)
45 	{
46 		auto ret = typeof(this)();
47 		ret.deserialize(data);
48 		return ret;
49 	}
50 
51 	public this(R)(auto ref R __data)
52 		if(isProtoInputRange!R)
53 	{
54 		deserialize(__data);
55 	}
56 
57 	ubyte[] serialize() const
58 	{
59 		import std.array : appender;
60 		auto __a = appender!(ubyte[]);
61 		serializeTo(__a);
62 		return __a.data;
63 	}
64 
65 	void serializeTo(R)(ref R __r) const
66 		if(isProtoOutputRange!R)
67 	{
68 		import dproto.attributes;
69 		foreach(__member; ProtoFields!this) {
70 			alias __field = Identity!(__traits(getMember, this, __member));
71 			serializeField!__field(__r);
72 		}
73 	}
74 
75 	void deserialize(R)(auto ref R __r)
76 		if(isProtoInputRange!R)
77 	{
78 		import dproto.attributes;
79 		import painlesstraits : getAnnotation;
80 
81 		while(!__r.empty()) {
82 			auto __msgdata = __r.readVarint();
83 			bool __matched = false;
84 			foreach(__member; ProtoFields!this) {
85 				alias __field = Identity!(__traits(getMember, this, __member));
86 				alias __fieldData = getAnnotation!(__field, ProtoField);
87 				if(__msgdata.msgNum == __fieldData.fieldNumber) {
88 					__r.putProtoVal!(__field)(__msgdata);
89 					__matched = true;
90 				}
91 			}
92 			if(!__matched) {
93 				defaultDecode(__msgdata, __r);
94 			}
95 		}
96 	}
97 
98 	void mergeFrom(this This)(auto ref This rhs)
99 	{
100 		import dproto.attributes;
101 
102 		foreach(__member; ProtoFields!this) {
103 			alias __lmember = Identity!(__traits(getMember, this, __member));
104 			alias T = typeof(__lmember);
105 
106 			static if(is(T : const string) || is(T : const(ubyte)[])) {
107 				__lmember = __traits(getMember, rhs, __member);
108 			} else static if(is(T : const(U)[], U)) {
109 				__lmember ~= __traits(getMember, rhs, __member);
110 			} else {
111 				__lmember = __traits(getMember, rhs, __member);
112 			}
113 		}
114 	}
115 
116 	version(Have_painlessjson) {
117 		auto toJson() const {
118 			import painlessjson;
119 			import std.conv : to;
120 			return painlessjson.toJSON(this).to!string;
121 		}
122 	}
123 }
124 
125 template ProtoFields(alias self)
126 {
127 	import std.typetuple : Filter, TypeTuple, Erase;
128 
129 	alias Field(alias F) = Identity!(__traits(getMember, self, F));
130 	alias HasProtoField(alias F) = hasValueAnnotation!(Field!F, ProtoField);
131 	alias AllMembers = TypeTuple!(__traits(allMembers, typeof(self)));
132 	alias ProtoFields = Filter!(HasProtoField, Erase!("dproto", AllMembers));
133 }
134 
135 template protoDefault(T) {
136 	import std.traits : isFloatingPoint;
137 	static if(isFloatingPoint!T) {
138 		enum protoDefault = 0.0;
139 	} else static if(is(T : const string)) {
140 		enum protoDefault = "";
141 	} else static if(is(T : const ubyte[])) {
142 		enum protoDefault = [];
143 	} else {
144 		enum protoDefault = T.init;
145 	}
146 }
147 
148 void serializeField(alias field, R)(ref R r) const
149     if (isProtoOutputRange!R)
150 {
151 	alias fieldType = typeof(field);
152 	enum fieldData = getAnnotation!(field, ProtoField);
153 	// Serialize if required or if the value isn't the (proto) default
154 	enum isNullable = is(fieldType == Nullable!U, U);
155 	bool needsToSerialize;
156 	static if (isNullable) {
157 		needsToSerialize = ! field.isNull;
158 	} else {
159 		needsToSerialize = hasValueAnnotation!(field, Required)
160 		    || (field != protoDefault!fieldType);
161 	}
162 
163 	// If we still don't need to serialize, we're done here
164 	if (!needsToSerialize)
165 	{
166 		return;
167 	}
168 	static if(isNullable) {
169 		const rawField = field.get;
170 	} else {
171 		const rawField = field;
172 	}
173 
174 	enum isPacked = hasValueAnnotation!(field, Packed);
175 	enum isPackType = is(fieldType == enum) || fieldData.wireType.isBuiltinType;
176 	static if (isPacked && isArray!fieldType && isPackType)
177 		alias serializer = serializePackedProto;
178 	else
179 		alias serializer = serializeProto;
180 	serializer!fieldData(rawField, r);
181 }
182 
183 void putProtoVal(alias __field, R)(auto ref R r, ulong __msgdata)
184 	if (isProtoInputRange!R)
185 {
186 	import std.range : ElementType, takeExactly, popFrontExactly;
187 	import std.array : empty; // Needed if R is an array
188 	import std.traits : isSomeString, isDynamicArray;
189 
190 	alias T = typeof(__field);
191 	enum wireType = getAnnotation!(__field, ProtoField).wireType;
192 	enum isBinString(T) = Identity!(is(T : const(ubyte)[]));
193 	static if (isDynamicArray!T && !(isSomeString!T || isBinString!T))
194 	{
195 		ElementType!T u;
196 		if (wireType.msgType != PACKED_MSG_TYPE && __msgdata.wireType == PACKED_MSG_TYPE)
197 		{
198 			size_t nelems = cast(size_t)r.readVarint();
199 			auto packeddata = takeExactly(r, nelems);
200 			while(!packeddata.empty)
201 			{
202 				u.putSingleProtoVal!wireType(packeddata);
203 				__field ~= u;
204 			}
205 			r.popFrontExactly(nelems);
206 		}
207 		else
208 		{
209 			u.putSingleProtoVal!wireType(r);
210 			__field ~= u;
211 		}
212 	}
213 	else
214 	{
215 		__field.putSingleProtoVal!wireType(r);
216 	}
217 }
218 
219 void putSingleProtoVal(string wireType, T, R)(ref T t, auto ref R r)
220 	if(isProtoInputRange!R)
221 {
222 	import std.conv : to;
223 	static if(is(T : Nullable!U, U)) {
224 		U t_tmp;
225 		t_tmp.putSingleProtoVal!wireType(r);
226 		t = t_tmp;
227 	} else static if(wireType.isBuiltinType) {
228 		t = r.readProto!wireType().to!T();
229 	} else static if(is(T == enum)) {
230 		t = r.readProto!ENUM_SERIALIZATION().to!T();
231 	} else {
232 		auto myData = r.readProto!"bytes"();
233 		return t.deserialize(myData);
234 	}
235 }
236 
237 void serializeProto(alias fieldData, T, R)(const T data, ref R r)
238 	if(isProtoOutputRange!R)
239 {
240 	static import dproto.serialize;
241 	static if(is(T : const string)) {
242 		r.toVarint(fieldData.header);
243 		r.writeProto!"string"(data);
244 	} else static if(is(T : const(ubyte)[])) {
245 		r.toVarint(fieldData.header);
246 		r.writeProto!"bytes"(data);
247 	} else static if(is(T : const(T)[], T)) {
248 		foreach(val; data) {
249 			serializeProto!fieldData(val, r);
250 		}
251 	} else static if(fieldData.wireType.isBuiltinType) {
252 		r.toVarint(fieldData.header);
253 		enum wt = fieldData.wireType;
254 		r.writeProto!(wt)(data);
255 	} else static if(is(T == enum)) {
256 		r.toVarint(ENUM_SERIALIZATION.msgType | (fieldData.fieldNumber << 3));
257 		r.writeProto!ENUM_SERIALIZATION(data);
258 	} else static if(__traits(compiles, data.serialize())) {
259 		r.toVarint(fieldData.header);
260 		dproto.serialize.CntRange cnt;
261 		data.serializeTo(cnt);
262 		r.toVarint(cnt.cnt);
263 		data.serializeTo(r);
264 	} else {
265 		static assert(0, "Unknown serialization");
266 	}
267 }
268 
269 void serializePackedProto(alias fieldData, T, R)(const T data, ref R r)
270 	if(isProtoOutputRange!R)
271 {
272 	static assert(fieldData.wireType.isBuiltinType,
273 			"Cannot have packed repeated message");
274 	if(data.length) {
275 		dproto.serialize.CntRange cnt;
276 		static if(is(T == enum)) {
277 			enum wt = ENUM_SERIALIZATION.msgType;
278 		} else {
279 			enum wt = fieldData.wireType;
280 		}
281 		foreach (ref e; data)
282 			cnt.writeProto!wt(e);
283 		toVarint(r, PACKED_MSG_TYPE | (fieldData.fieldNumber << 3));
284 		toVarint(r, cnt.cnt);
285 		foreach (ref e; data)
286 			r.writeProto!wt(e);
287 	}
288 }