1 /*******************************************************************************
2  * User-Defined Attributes used to tag fields as dproto-serializable
3  *
4  * Authors: Matthew Soucy, msoucy@csh.rit.edu
5  * Date: May 6, 2015
6  * Version: 1.3.0
7  */
8 module dproto.attributes;
9 
10 import dproto.serialize;
11 import painlesstraits : getAnnotation, hasValueAnnotation;
12 import dproto.compat;
13 
14 import std.traits : isArray, Identity;
15 import std.typecons : Nullable;
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 		import std.traits;
70 		foreach(__member; ProtoFields!this) {
71 			alias __field = Identity!(__traits(getMember, this, __member));
72 			serializeField!__field(__r);
73 		}
74 	}
75 
76 	void deserialize(R)(auto ref R __r)
77 		if(isProtoInputRange!R)
78 	{
79 		import std.traits;
80 		import dproto.attributes;
81 		import painlesstraits : getAnnotation;
82 
83 		while(!__r.empty()) {
84 			auto __msgdata = __r.readVarint();
85 			bool __matched = false;
86 			foreach(__member; ProtoFields!this) {
87 				alias __field = Identity!(__traits(getMember, this, __member));
88 				alias __fieldData = getAnnotation!(__field, ProtoField);
89 				if(__msgdata.msgNum == __fieldData.fieldNumber) {
90 					__r.putProtoVal!(__field)(__msgdata);
91 					__matched = true;
92 				}
93 			}
94 			if(!__matched) {
95 				defaultDecode(__msgdata, __r);
96 			}
97 		}
98 	}
99 
100 	version(Have_painlessjson) {
101 		auto toJson() const {
102 			import painlessjson;
103 			import std.conv : to;
104 			return painlessjson.toJSON(this).to!string;
105 		}
106 	}
107 }
108 
109 template ProtoFields(alias self)
110 {
111 	import std.typetuple : Filter, TypeTuple, Erase;
112 
113 	alias Field(alias F) = Identity!(__traits(getMember, self, F));
114 	alias HasProtoField(alias F) = hasValueAnnotation!(Field!F, ProtoField);
115 	alias AllMembers = TypeTuple!(__traits(allMembers, typeof(self)));
116 	alias ProtoFields = Filter!(HasProtoField, Erase!("dproto", AllMembers));
117 }
118 
119 template protoDefault(T) {
120 	import std.traits : isFloatingPoint;
121 	static if(isFloatingPoint!T) {
122 		enum protoDefault = 0.0;
123 	} else static if(is(T : const string)) {
124 		enum protoDefault = "";
125 	} else static if(is(T : const ubyte[])) {
126 		enum protoDefault = [];
127 	} else {
128 		enum protoDefault = T.init;
129 	}
130 }
131 
132 void serializeField(alias field, R)(ref R r) const
133     if (isProtoOutputRange!R)
134 {
135 	alias fieldType = typeof(field);
136 	enum fieldData = getAnnotation!(field, ProtoField);
137 	// Serialize if required or if the value isn't the (proto) default
138 	enum isNullable = is(fieldType == Nullable!U, U);
139 	bool needsToSerialize;
140 	static if (isNullable) {
141 		needsToSerialize = ! field.isNull;
142 	} else {
143 		needsToSerialize = hasValueAnnotation!(field, Required)
144 		    || (field != protoDefault!fieldType);
145 	}
146 
147 	// If we still don't need to serialize, we're done here
148 	if (!needsToSerialize)
149 	{
150 		return;
151 	}
152 	static if(isNullable) {
153 		const rawField = field.get;
154 	} else {
155 		const rawField = field;
156 	}
157 
158 	enum isPacked = hasValueAnnotation!(field, Packed);
159 	enum isPackType = is(fieldType == enum) || fieldData.wireType.isBuiltinType;
160 	static if (isPacked && isArray!fieldType && isPackType)
161 		alias serializer = serializePackedProto;
162 	else
163 		alias serializer = serializeProto;
164 	serializer!fieldData(rawField, r);
165 }
166 
167 void putProtoVal(alias __field, R)(auto ref R r, ulong __msgdata)
168 	if (isProtoInputRange!R)
169 {
170 	import std.range : ElementType, takeExactly, popFrontExactly;
171 	import std.array : empty; // Needed if R is an array
172 	import std.traits : isSomeString, isDynamicArray;
173 
174 	alias T = typeof(__field);
175 	enum wireType = getAnnotation!(__field, ProtoField).wireType;
176 	enum isBinString(T) = Identity!(is(T : const(ubyte)[]));
177 	static if (isDynamicArray!T && !(isSomeString!T || isBinString!T))
178 	{
179 		ElementType!T u;
180 		if (wireType.msgType != PACKED_MSG_TYPE && __msgdata.wireType == PACKED_MSG_TYPE)
181 		{
182 			ulong nelems = r.readVarint();
183 			auto packeddata = takeExactly(r, nelems);
184 			while(!packeddata.empty)
185 			{
186 				u.putSingleProtoVal!wireType(packeddata);
187 				__field ~= u;
188 			}
189 			r.popFrontExactly(nelems);
190 		}
191 		else
192 		{
193 			u.putSingleProtoVal!wireType(r);
194 			__field ~= u;
195 		}
196 	}
197 	else
198 	{
199 		__field.putSingleProtoVal!wireType(r);
200 	}
201 }
202 
203 void putSingleProtoVal(string wireType, T, R)(ref T t, auto ref R r)
204 	if(isProtoInputRange!R)
205 {
206 	import std.conv : to;
207 	static if(is(T : Nullable!U, U)) {
208 		U t_tmp;
209 		t_tmp.putSingleProtoVal!wireType(r);
210 		t = t_tmp;
211 	} else static if(wireType.isBuiltinType) {
212 		t = r.readProto!wireType().to!T();
213 	} else static if(is(T == enum)) {
214 		t = r.readProto!ENUM_SERIALIZATION().to!T();
215 	} else {
216 		auto myData = r.readProto!"bytes"();
217 		return t.deserialize(myData);
218 	}
219 }
220 
221 void serializeProto(alias fieldData, T, R)(const T data, ref R r)
222 	if(isProtoOutputRange!R)
223 {
224 	static import dproto.serialize;
225 	static if(is(T : const string)) {
226 		r.toVarint(fieldData.header);
227 		r.writeProto!"string"(data);
228 	} else static if(is(T : const(ubyte)[])) {
229 		r.toVarint(fieldData.header);
230 		r.writeProto!"bytes"(data);
231 	} else static if(is(T : const(T)[], T)) {
232 		foreach(val; data) {
233 			serializeProto!fieldData(val, r);
234 		}
235 	} else static if(fieldData.wireType.isBuiltinType) {
236 		r.toVarint(fieldData.header);
237 		enum wt = fieldData.wireType;
238 		r.writeProto!(wt)(data);
239 	} else static if(is(T == enum)) {
240 		r.toVarint(ENUM_SERIALIZATION.msgType | (fieldData.fieldNumber << 3));
241 		r.writeProto!ENUM_SERIALIZATION(data);
242 	} else static if(__traits(compiles, data.serialize())) {
243 		r.toVarint(fieldData.header);
244 		dproto.serialize.CntRange cnt;
245 		data.serializeTo(cnt);
246 		r.toVarint(cnt.cnt);
247 		data.serializeTo(r);
248 	} else {
249 		static assert(0, "Unknown serialization");
250 	}
251 }
252 
253 void serializePackedProto(alias fieldData, T, R)(const T data, ref R r)
254 	if(isProtoOutputRange!R)
255 {
256 	static assert(fieldData.wireType.isBuiltinType,
257 			"Cannot have packed repeated message");
258 	if(data.length) {
259 		dproto.serialize.CntRange cnt;
260 		static if(is(T == enum)) {
261 			enum wt = ENUM_SERIALIZATION.msgType;
262 		} else {
263 			enum wt = fieldData.wireType;
264 		}
265 		foreach (ref e; data)
266 			cnt.writeProto!wt(e);
267 		toVarint(r, PACKED_MSG_TYPE | (fieldData.fieldNumber << 3));
268 		toVarint(r, cnt.cnt);
269 		foreach (ref e; data)
270 			r.writeProto!wt(e);
271 	}
272 }