1 /** 2 * A collection of compile-time functions to help in identifying stream types 3 * and related flavors of them. 4 * 5 * Streams come in two main flavors: ${B input} and ${B output} streams. 6 * 7 * ${B Input streams} are defined by the presence of a read function: 8 * ```d 9 * StreamResult readFromStream(DataType[] buffer) 10 * ``` 11 * 12 * ${B Output streams} are defined by the presence of a write function: 13 * ```d 14 * StreamResult writeToStream(DataType[] buffer) 15 * ``` 16 * 17 * Usually these functions can be used as [Template Constraints](https://dlang.org/spec/template.html#template_constraints) 18 * when defining your own functions and symbols to work with streams. 19 * ```d 20 * void useBytes(S)(S stream) if (isInputStream!(S, ubyte)) { 21 * ubyte[8192] buffer; 22 * StreamResult result = stream.readFromStream(buffer); 23 * if (!result.hasError) { 24 * // Do something with the data. 25 * } 26 * } 27 * ``` 28 */ 29 module streams.primitives; 30 31 import streams.utils : Optional, Either; 32 import std.range : ElementType; 33 import std.traits : isCallable, ReturnType, Parameters, isDynamicArray; 34 35 private const INPUT_STREAM_METHOD = "readFromStream"; 36 private const OUTPUT_STREAM_METHOD = "writeToStream"; 37 private const FLUSHABLE_STREAM_METHOD = "flushStream"; 38 private const CLOSABLE_STREAM_METHOD = "closeStream"; 39 private const SEEKABLE_STREAM_METHOD = "seekInStream"; 40 41 /** 42 * An error that occurred during a stream operation, which includes a short 43 * message, as well as an integer code which is usually the last stream 44 * operation return code. 45 */ 46 struct StreamError { 47 const(char[]) message; 48 const int code; 49 } 50 51 /** 52 * A convenience alias for an optional stream error, which is a common return 53 * type for many stream methods. 54 */ 55 alias OptionalStreamError = Optional!StreamError; 56 57 /** 58 * Either a number of items that have been read or written, or a stream error, 59 * as a common result type for many stream operations. 60 * 61 * As an instance of `Either`, it offers the following methods: 62 * - `result.hasError` to check if the result has an error. 63 * - `result.hasCount` to check if the result has an element count. 64 * - `result.error` to get the `StreamError` instance, if `result.hasError` returns `true`. 65 * - `result.count` to get the number of elements read or written, if `result.hasCount` returns `true`. 66 */ 67 alias StreamResult = Either!(uint, "count", StreamError, "error"); 68 69 /** 70 * Determines if the given template argument is some form of input stream. 71 * Returns: `true` if the given argument is an input stream type. 72 */ 73 bool isSomeInputStream(StreamType)() { 74 // Note: We use a cascading static check style so the compiler runs these checks in this order. 75 static if (__traits(hasMember, StreamType, INPUT_STREAM_METHOD)) { 76 alias func = __traits(getMember, StreamType, INPUT_STREAM_METHOD); 77 static if (isCallable!func && is(ReturnType!func == StreamResult)) { 78 static if (Parameters!func.length == 1) { 79 return isDynamicArray!(Parameters!func[0]); 80 } else { return false; } 81 } else { return false; } 82 } else { return false; } 83 } 84 85 unittest { 86 struct S1 { 87 StreamResult readFromStream(ubyte[] buffer) { return StreamResult(0); } // cov-ignore 88 } 89 assert(isSomeInputStream!S1); 90 // Check that pointers to streams are also considered as streams. 91 assert(isSomeInputStream!(S1*)); 92 // But double or triple pointers (or anything else) are not! 93 assert(!isSomeInputStream!(S1**)); 94 assert(!isSomeInputStream!(S1***)); 95 struct S2 { 96 StreamResult readFromStream(bool[] buffer) { return StreamResult(42); } // cov-ignore 97 } 98 assert(isSomeInputStream!S2); 99 struct S3 { 100 StreamResult readFromStream(bool[] buffer, int otherArg) { return StreamResult(0); } // cov-ignore 101 } 102 assert(!isSomeInputStream!S3); 103 struct S4 { 104 void readFromStream(long[] buffer) {} 105 } 106 assert(!isSomeInputStream!S4); 107 struct S5 { 108 StreamResult readFromStream = StreamResult(10); 109 } 110 assert(!isSomeInputStream!S5); 111 struct S6 {} 112 assert(!isSomeInputStream!S6); 113 114 version (D_BetterC) {} else { 115 interface I1 { 116 StreamResult readFromStream(ubyte[] buffer); 117 } 118 assert(isSomeInputStream!I1); 119 class C1 { 120 StreamResult readFromStream(ubyte[] buffer) { return StreamResult(0); } // cov-ignore 121 } 122 assert(isSomeInputStream!C1); 123 } 124 } 125 126 /** 127 * Determines if the given template argument is some form of output stream. 128 * Returns: `true` if the given argument is an output stream type. 129 */ 130 bool isSomeOutputStream(StreamType)() { 131 // Note: We use a cascading static check style so the compiler runs these checks in this order. 132 static if (__traits(hasMember, StreamType, OUTPUT_STREAM_METHOD)) { 133 alias func = __traits(getMember, StreamType, OUTPUT_STREAM_METHOD); 134 static if (isCallable!func && is(ReturnType!func == StreamResult)) { 135 static if (Parameters!func.length == 1) { 136 return isDynamicArray!(Parameters!func[0]); 137 } else { return false; } 138 } else { return false; } 139 } else { return false; } 140 } 141 142 unittest { 143 struct S1 { 144 StreamResult writeToStream(ubyte[] buffer) { return StreamResult(0); } // cov-ignore 145 } 146 assert(isSomeOutputStream!S1); 147 // Check that pointers to output streams are also considered streams (but not double and so on). 148 assert(isSomeOutputStream!(S1*)); 149 assert(!isSomeOutputStream!(S1**)); 150 assert(!isSomeOutputStream!(S1***)); 151 struct S2 { 152 StreamResult writeToStream(bool[] buffer) { return StreamResult(42); } // cov-ignore 153 } 154 assert(isSomeOutputStream!S2); 155 struct S3 { 156 StreamResult writeToStream(bool[] buffer, int otherArg) { return StreamResult(0); } // cov-ignore 157 } 158 assert(!isSomeOutputStream!S3); 159 struct S4 { 160 void writeToStream(long[] buffer) {} 161 } 162 assert(!isSomeOutputStream!S4); 163 struct S5 { 164 StreamResult writeToStream = StreamResult(10); 165 } 166 assert(!isSomeOutputStream!S5); 167 struct S6 {} 168 assert(!isSomeOutputStream!S6); 169 } 170 171 /** 172 * A template that evaluates to the type of a given input or output stream. 173 * Params: 174 * S = The stream to get the type of. 175 */ 176 template StreamType(S) if (isSomeStream!S) { 177 static if (isSomeInputStream!S) { 178 alias StreamType = ElementType!(Parameters!(__traits(getMember, S, INPUT_STREAM_METHOD))[0]); 179 } else { 180 alias StreamType = ElementType!(Parameters!(__traits(getMember, S, OUTPUT_STREAM_METHOD))[0]); 181 } 182 } 183 184 unittest { 185 struct S1 { 186 StreamResult readFromStream(bool[] buffer) { 187 return StreamResult(0); // cov-ignore 188 } 189 } 190 assert(is(StreamType!S1 == bool)); 191 assert(is(StreamType!(S1*) == bool)); 192 } 193 194 /** 195 * Determines if the given stream type is an input stream for reading data of 196 * the given type. 197 * Returns: `true` if the given stream type is an input stream. 198 */ 199 bool isInputStream(StreamType, DataType)() { 200 static if (isSomeInputStream!StreamType) { 201 return is(Parameters!(__traits(getMember, StreamType, INPUT_STREAM_METHOD))[0] == DataType[]); 202 } else { 203 return false; 204 } 205 } 206 207 unittest { 208 // Test a valid input stream. 209 struct S1 { 210 StreamResult readFromStream(ubyte[] buffer) { 211 return StreamResult(0); // cov-ignore 212 } 213 } 214 assert(isInputStream!(S1, ubyte)); 215 216 // Test a few invalid input streams. 217 struct S2 {} 218 assert(!isInputStream!(S2, ubyte)); 219 struct S3 { 220 void readFromStream(ubyte[] buffer) { 221 // Invalid return type! 222 } 223 } 224 assert(!isInputStream!(S3, ubyte)); 225 struct S4 { 226 StreamResult readFromStream() { 227 return StreamResult(0); // cov-ignore 228 } 229 } 230 assert(!isInputStream!(S4, ubyte)); 231 232 version (D_BetterC) {} else { 233 class C1 { 234 StreamResult readFromStream(char[] buffer) { 235 return StreamResult(0); // cov-ignore 236 } 237 } 238 assert(isInputStream!(C1, char)); 239 } 240 } 241 242 /** 243 * Determines if the given stream type is an output stream for writing data of 244 * the given type. 245 * Returns: `true` if the given stream type is an output stream. 246 */ 247 bool isOutputStream(StreamType, DataType)() { 248 static if (isSomeOutputStream!StreamType) { 249 return is(Parameters!(__traits(getMember, StreamType, OUTPUT_STREAM_METHOD))[0] == DataType[]); 250 } else { 251 return false; 252 } 253 } 254 255 unittest { 256 // Test a valid output stream. 257 struct S1 { 258 StreamResult writeToStream(ref ubyte[] buffer) { 259 return StreamResult(0); // cov-ignore 260 } 261 } 262 assert(isOutputStream!(S1, ubyte)); 263 264 // Test a few invalid output streams. 265 struct S2 {} 266 assert(!isOutputStream!(S2, ubyte)); 267 struct S3 { 268 void writeToStream(ubyte[] buffer) { 269 // Invalid return type! 270 } 271 } 272 assert(!isOutputStream!(S3, ubyte)); 273 struct S4 { 274 StreamResult writeToStream() { 275 return StreamResult(0); // cov-ignore 276 } 277 } 278 assert(!isOutputStream!(S4, ubyte)); 279 } 280 281 /** 282 * Determines if the given template argument is a stream of any kind; that is, 283 * it is at least implementing the functions required to be an input or output 284 * stream. 285 * Returns: `true` if the given argument is some stream. 286 */ 287 bool isSomeStream(StreamType)() { 288 return isSomeInputStream!StreamType || isSomeOutputStream!StreamType; 289 } 290 291 unittest { 292 struct S1 { 293 StreamResult readFromStream(ubyte[] buffer) { 294 return StreamResult(0); // cov-ignore 295 } 296 } 297 assert(isSomeStream!S1); 298 struct S2 { 299 StreamResult writeToStream(ubyte[] buffer) { 300 return StreamResult(0); // cov-ignore 301 } 302 } 303 assert(isSomeStream!S2); 304 struct S3 {} 305 assert(!isSomeStream!S3); 306 } 307 308 /** 309 * Determines if the given stream type is an input or output stream for data of 310 * the given type. 311 * Returns: `true` if the stream type is an input or output stream for the 312 * given data type. 313 */ 314 bool isSomeStream(StreamType, DataType)() { 315 return isInputStream!(StreamType, DataType) || isOutputStream!(StreamType, DataType); 316 } 317 318 /** 319 * Determines if the given stream type is an input stream for `ubyte` elements. 320 * Returns: `true` if the stream type is a byte input stream. 321 */ 322 bool isByteInputStream(StreamType)() { 323 return isInputStream!(StreamType, ubyte); 324 } 325 326 /** 327 * Determines if the given stream type is an output stream for `ubyte` elements. 328 * Returns: `true` if the stream type is a byte output stream. 329 */ 330 bool isByteOutputStream(StreamType)() { 331 return isOutputStream!(StreamType, ubyte); 332 } 333 334 /** 335 * Determines if the given template argument is a closable stream type, which 336 * defines a `Optional!StreamError closeStream()` method as a means to close 337 * and/or deallocate the underlying resource that the stream reads from or 338 * writes to. 339 * 340 * Returns: `true` if the given argument is a closable stream. 341 */ 342 bool isClosableStream(S)() { 343 static if ( 344 isSomeStream!S && 345 __traits(hasMember, S, CLOSABLE_STREAM_METHOD) && 346 isCallable!(__traits(getMember, S, CLOSABLE_STREAM_METHOD)) 347 ) { 348 alias closeFunction = __traits(getMember, S, CLOSABLE_STREAM_METHOD); 349 alias params = Parameters!closeFunction; 350 return (is(ReturnType!closeFunction == OptionalStreamError) && params.length == 0); 351 } else { 352 return false; 353 } 354 } 355 356 unittest { 357 struct S1 { 358 StreamResult readFromStream(ubyte[] buffer) { 359 return StreamResult(0); // cov-ignore 360 } 361 OptionalStreamError closeStream() { 362 return OptionalStreamError.init; // cov-ignore 363 } 364 } 365 assert(isClosableStream!S1); 366 assert(isClosableStream!(S1*)); 367 struct S2 { 368 StreamResult readFromStream(ubyte[] buffer) { 369 return StreamResult(0); // cov-ignore 370 } 371 } 372 assert(!isClosableStream!S2); 373 struct S3 {} 374 assert(!isClosableStream!S3); 375 } 376 377 /** 378 * Determines if the given template argument is a flushable stream type, which 379 * is any output stream that defines a `Optional!StreamError flushStream()` 380 * method, which should cause any data buffered by the stream or its resources 381 * to be flushed. The exact nature of how a flush operates is implementation- 382 * dependent. 383 * Returns: `true` if the given argument is a flushable stream. 384 */ 385 bool isFlushableStream(S)() { 386 static if ( 387 isSomeOutputStream!S && 388 __traits(hasMember, S, FLUSHABLE_STREAM_METHOD) && 389 isCallable!(__traits(getMember, S, FLUSHABLE_STREAM_METHOD)) 390 ) { 391 alias flushFunction = __traits(getMember, S, FLUSHABLE_STREAM_METHOD); 392 alias params = Parameters!flushFunction; 393 return (is(ReturnType!flushFunction == OptionalStreamError) && params.length == 0); 394 } else { 395 return false; 396 } 397 } 398 399 unittest { 400 struct S1 { 401 StreamResult writeToStream(ubyte[] buffer) { 402 return StreamResult(0); // cov-ignore 403 } 404 OptionalStreamError flushStream() { 405 return OptionalStreamError.init; // cov-ignore 406 } 407 } 408 assert(isFlushableStream!S1); 409 assert(isFlushableStream!(S1*)); 410 struct S2 { 411 StreamResult writeToStream(ubyte[] buffer) { 412 return StreamResult(0); // cov-ignore 413 } 414 } 415 assert(!isFlushableStream!S2); 416 struct S3 {} 417 assert(!isFlushableStream!S3); 418 } 419 420 /** 421 * Determines if the given template argument is a seekable stream type, which 422 * is any stream, input or output, that defines a `Optional!StreamError seekInStream(ulong offset)` 423 * function for seeking to a particular position in a stream, specified by the 424 * `offset` in terms of elements. 425 * Returns: `true` if the given argument is a seekable stream. 426 */ 427 bool isSeekableStream(S)() { 428 static if ( 429 isSomeStream!S && 430 __traits(hasMember, S, SEEKABLE_STREAM_METHOD) && 431 isCallable!(__traits(getMember, S, SEEKABLE_STREAM_METHOD)) 432 ) { 433 alias seekFunction = __traits(getMember, S, SEEKABLE_STREAM_METHOD); 434 alias params = Parameters!seekFunction; 435 return ( 436 is(ReturnType!seekFunction == OptionalStreamError) && 437 params.length == 1 && is(params[0] == ulong) 438 ); 439 } else { 440 return false; 441 } 442 } 443 444 /** 445 * Determines if the given template argument is a pointer to a stream. 446 * Returns: `true` if the given argument is a pointer to a stream. 447 */ 448 bool isPointerToStream(S)() { 449 return is(S == B*, B) && isSomeStream!S; 450 } 451 452 unittest { 453 struct S1 { 454 StreamResult readFromStream(ubyte[] buf) { 455 return StreamResult(0); // cov-ignore 456 } 457 } 458 struct S2 { 459 void doStuff() {} // cov-ignore 460 } 461 assert(isPointerToStream!(S1*)); 462 assert(!isPointerToStream!S1); 463 assert(!isPointerToStream!(S1**)); 464 assert(!isPointerToStream!(S2*)); 465 } 466 467 /** 468 * Determines if the given template argument is a direct stream type, and that 469 * it is not a pointer. If true, then this implies that the caller "owns" the 470 * stream, and the stream should not be used outside of owner's scope. 471 * Returns: `true` if the given argument is a stream, and not a pointer to one. 472 */ 473 bool isNonPointerStream(S)() { 474 return !is(S == B*, B) && isSomeStream!S; 475 } 476 477 unittest { 478 struct S1 { 479 StreamResult readFromStream(ubyte[] buf) { 480 return StreamResult(0); // cov-ignore 481 } 482 } 483 struct S2 { 484 void doStuff() {} // cov-ignore 485 } 486 assert(isNonPointerStream!(S1)); 487 assert(!isNonPointerStream!(S1*)); 488 assert(!isNonPointerStream!(S1**)); 489 assert(!isNonPointerStream!(S2*)); 490 assert(!isNonPointerStream!S2); 491 } 492 493 /** 494 * An input stream that always reads 0 elements. 495 */ 496 struct NoOpInputStream(T) { 497 /** 498 * Reads zero elements. 499 * Params: 500 * buffer = A buffer. 501 * Returns: Always 0. 502 */ 503 StreamResult readFromStream(T[] buffer) { 504 return StreamResult(0); 505 } 506 } 507 508 /** 509 * An output stream that always writes 0 elements. 510 */ 511 struct NoOpOutputStream(T) { 512 /** 513 * Writes zero elements. 514 * Params: 515 * buffer = A buffer. 516 * Returns: Always 0. 517 */ 518 StreamResult writeToStream(T[] buffer) { 519 return StreamResult(0); 520 } 521 } 522 523 /** 524 * An input stream that always returns an error response. 525 */ 526 struct ErrorInputStream(T) { 527 /** 528 * Always emits an error response when called. 529 * Params: 530 * buffer = A buffer. 531 * Returns: A stream error. 532 */ 533 StreamResult readFromStream(T[] buffer) { 534 return StreamResult(StreamError("An error occurred.", -1)); 535 } 536 } 537 538 /** 539 * An output stream that always returns an error response. 540 */ 541 struct ErrorOutputStream(T) { 542 /** 543 * Always emits an error response when called. 544 * Params: 545 * buffer = A buffer. 546 * Returns: A stream error. 547 */ 548 StreamResult writeToStream(T[] buffer) { 549 return StreamResult(StreamError("An error occurred.", -1)); 550 } 551 } 552 553 unittest { 554 auto s1 = NoOpInputStream!ubyte(); 555 ubyte[3] buffer; 556 assert(s1.readFromStream(buffer) == StreamResult(0)); 557 assert(buffer == [0, 0, 0]); 558 559 auto s2 = NoOpOutputStream!ubyte(); 560 assert(s2.writeToStream(buffer) == StreamResult(0)); 561 562 auto s3 = ErrorInputStream!ubyte(); 563 assert(s3.readFromStream(buffer).hasError); 564 565 auto s4 = ErrorOutputStream!ubyte(); 566 assert(s4.writeToStream(buffer).hasError); 567 }