1 /****************************************************************************** 2 3 Provides basic utilities to work with version information in structures. 4 5 Most common idiom used in application code is checking for 6 version support: 7 8 --- 9 static if (Version.Info!(S).exists) 10 { 11 } 12 --- 13 14 It is also possible to manually figure out whole version increment 15 chain based on provided metadata (though use cases for that 16 are supposed to be rare) 17 18 Copyright: 19 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 20 All rights reserved. 21 22 License: 23 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 24 Alternatively, this file may be distributed under the terms of the Tango 25 3-Clause BSD License (see LICENSE_BSD.txt for details). 26 27 *******************************************************************************/ 28 29 module ocean.util.serialize.Version; 30 31 32 import ocean.meta.types.Qualifiers; 33 import ocean.core.Verify; 34 import ocean.core.Buffer; 35 36 version (unittest) import ocean.core.Test; 37 38 /******************************************************************************* 39 40 Tag type that denotes missing version type for either next or previous 41 version of a struct. 42 43 *******************************************************************************/ 44 45 public struct MissingVersion 46 { 47 enum exists = false; 48 enum ubyte number = 0; 49 } 50 51 /******************************************************************************* 52 53 Namespace struct is desired because most of symbols have very common and 54 generic names. 55 56 *******************************************************************************/ 57 58 struct Version 59 { 60 /*************************************************************************** 61 62 The type of the version number tag that is prepended to serialised data. 63 64 ***************************************************************************/ 65 66 public alias ubyte Type; 67 68 /*************************************************************************** 69 70 Evaluates to version information of S if S is a versioned struct: 71 72 - exists: true if S is a versioned struct or false if S is a struct 73 without version or not a struct. If false the other constants are 74 not defined. 75 - number: The value of the struct version (S.Info), expected to 76 be of type Type. 77 - next/prev: Info for S.StructNext or S.StructPrev, respectively 78 - type : type of struct this info belongs to 79 80 next/prev are recursive instances of this template so they can be 81 checked for existence by Info!(S).next.exists 82 If that's true the next version number is Info!(S).next.number. 83 The next version itself can contain a next version, 84 use Info!(S).next.next.exists to check it out. 85 86 ***************************************************************************/ 87 88 template Info ( S ) 89 { 90 static if (is(S == struct) && is(typeof(S.StructVersion) V)) 91 { 92 static assert ( 93 S.StructVersion <= Version.Type.max, 94 S.stringof ~ ".StructVersion == " ~ 95 S.StructVersion.stringof ~ 96 ", but it must be lower than Version.Type.max" 97 ); 98 99 enum exists = true; 100 enum number = S.StructVersion; 101 102 alias S type; 103 104 static if (is(S.StructNext)) 105 { 106 alias Info!(S.StructNext) next; 107 } 108 else 109 { 110 alias MissingVersion next; // dummy 111 } 112 113 static if (is(S.StructPrevious)) 114 { 115 alias Info!(S.StructPrevious) prev; 116 } 117 else 118 { 119 alias MissingVersion prev; // dummy 120 } 121 } 122 else 123 { 124 alias MissingVersion Info; 125 } 126 } 127 128 unittest 129 { 130 struct S { } 131 static assert (is(Info!(S) == MissingVersion)); 132 static assert (! Info!(S).exists); 133 } 134 135 unittest 136 { 137 struct S1 { enum StructVersion = 1; } 138 alias Info!(S1) Ver; 139 static assert (Ver.exists); 140 static assert (Ver.number == 1); 141 static assert (is(Ver.next == MissingVersion)); 142 static assert (is(Ver.prev == MissingVersion)); 143 144 struct S2 { enum StructVersion = Version.Type.max + 1; } 145 static assert (!is(typeof(Info!(S2)))); 146 } 147 148 unittest 149 { 150 struct S 151 { 152 enum StructVersion = 1; 153 alias S StructPrevious; 154 alias S StructNext; 155 } 156 157 alias Info!(S) Ver; 158 159 static assert (Ver.exists); 160 static assert (is(Ver.next.type == S)); 161 static assert (is(Ver.prev.type == S)); 162 } 163 164 /*************************************************************************** 165 166 Assumes that input is versioned struct chunk and extracts version number 167 from it. Otherwise will return garbage 168 169 Params: 170 data = serialized struct data, untouched 171 ver = out parameter to store version number 172 173 Returns: 174 data slices after the version bytes 175 176 ***************************************************************************/ 177 178 static void[] extract ( void[] data, ref Version.Type ver ) 179 { 180 verify (data.length > Version.Type.sizeof); 181 182 ver = *(cast(Version.Type*) data.ptr); 183 return data[Version.Type.sizeof .. $]; 184 } 185 186 unittest 187 { 188 Version.Type V = 42; 189 void[] data = [ V, cast(Version.Type) 1, cast(Version.Type) 1 ]; 190 Version.Type ver; 191 auto data_unver = extract(data, ver); 192 test!("==")(ver, V); 193 test!("==")(data_unver.length, 2); 194 } 195 196 static const(void)[] extract ( in void[] data, ref Version.Type ver ) 197 { 198 return extract(cast(void[]) data, ver); 199 } 200 201 unittest 202 { 203 Version.Type V = 42; 204 void[] data = [ V, cast(Version.Type) 1, cast(Version.Type) 1 ]; 205 const(void[]) cdata = data; 206 Version.Type ver; 207 const(void)[] data_unver = extract(cdata, ver); 208 test!("==")(ver, V); 209 test!("==")(data_unver.length, 2); 210 } 211 212 213 /*************************************************************************** 214 215 Writes version data in the beginning of provided data buffer. Grows buffer 216 if it is too small. Call this function before actually writing any useful 217 payload to the buffer or it will be overwritten. 218 219 Params: 220 data = any byte buffer, will be modified to start with version info 221 ver = version number to inject 222 223 Returns: 224 slice of data after the version data. Use that slice to add actual 225 payload 226 227 ***************************************************************************/ 228 229 static void[] inject ( ref Buffer!(void) data, Version.Type ver ) 230 { 231 if (data.length < Version.Type.sizeof) 232 { 233 data.length = Version.Type.sizeof; 234 } 235 236 *(cast(Version.Type*) data[].ptr) = ver; 237 238 return data[Version.Type.sizeof .. data.length]; 239 } 240 241 /// ditto 242 static void[] inject ( ref void[] data, Version.Type ver ) 243 { 244 return inject(*cast(Buffer!(void)*) &data, ver); 245 } 246 247 unittest 248 { 249 Version.Type V = 42; 250 void[] data = [ cast(ubyte) 1, 2, 3 ]; 251 auto result = inject(data, V); 252 test!("is")(data.ptr + V.sizeof, result.ptr); 253 test!("==")(data, [ V, 2, 3 ][]); 254 } 255 }