1 module requests.rangeadapter;
2 
3 
4 /************************************************************************\
5 /*                                                                       *
6 /* I need this module to unify input content for POST/PUT requests.      *
7 /* Input content must be converted to ubyte[] or ubyte[][].              *
8 /* In latter case it will be transferred in transfer-Encoding: chunked.  *
9 /* I need to erase type of input range and convert it to bytes, because  *
10 /* interceptors have no access to input range type.                      *
11 /*                                                                       *
12  ************************************************************************/
13 
14 import std.format;
15 import std.range.primitives;
16 
17 private template rank(R) {
18     static if ( isInputRange!R ) {
19         enum size_t rank = 1 + rank!(ElementType!R);
20     } else {
21         enum size_t rank = 0;
22     }
23 }
24 
25 /*
26  * Convert rank 1 and 2 ranges to rank(2) range.
27  * Rank(1) converted to [[chunk]]
28  * Rank(2) used as is: [[chunk],[chunk],...]
29  */
30 private struct Adapter(R) {
31     R       _r;
32     long    _length = -1;
33 
34     enum _rank = rank!R;
35     static if ( _rank == 1 ){
36         bool _empty = false;
37     }
38     this(R r)
39     {
40         _r = r;
41         static if ( _rank == 1 && hasLength!R ) {
42             _length = _r.length;
43         }
44     }
45     immutable(ubyte)[] front()
46     {
47         static if ( _rank == 1 )
48         {
49             return cast(immutable(ubyte)[])_r[0..$]; // return whole array
50         }
51         static if ( _rank == 2 )
52         {
53             return cast(immutable(ubyte)[])_r.front; // return front chunk
54         }
55     }
56     bool empty()
57     {
58         static if ( _rank == 1 )
59         {
60             return _empty;
61         }
62         static if ( _rank == 2 )
63         {
64             return _r.empty;
65         }
66     }
67     void popFront()
68     {
69         static if ( _rank == 1 )
70         {
71             _empty = true;  // no data after pop for rank1
72         }
73         static if ( _rank == 2 )
74         {
75             _r.popFront;
76         }
77     }
78 }
79 
80 private auto ma(R)(R r) {
81     return new Adapter!R(r);
82 }
83 
84 ///
85 /// makeAdapter convert input range of ubytes (rank 1 or rank 2),
86 /// So that it can be used in POST requests
87 /// You can use it in Interceptors to modify/set POST data.
88 ///
89 public InputRangeAdapter makeAdapter(R)(R r) {
90     auto adapter = ma(r);
91     InputRangeAdapter result;
92     result._front = &adapter.front;
93     result._empty = &adapter.empty;
94     result._popFront = &adapter.popFront;
95     result._length = adapter._length;
96     return result;
97 }
98 
99 /********************************************
100  * This struct erase Type of the input range
101  * for POST requests, so that I can avoid
102  * templated functions for these requests.
103  * It is important for Interceptors which
104  * have no idea about user input types.
105  * Also it convert any rank 1 or 2 ranges to
106  * rank(2) so that I can use unified code later
107  ********************************************/
108 struct InputRangeAdapter {
109     private {
110         immutable(ubyte)[]  delegate() _front;
111         bool                delegate() _empty;
112         void                delegate() _popFront;
113         long                           _length = -1;
114     }
115     immutable(ubyte)[] front() {
116         return _front();
117     }
118     bool empty() const {
119         if ( _empty is null )
120         {
121             return true;
122         }
123         return _empty();
124     }
125     void popFront() {
126         _popFront();
127     }
128     long length() const {
129         return _length;
130     }
131 }
132 
133 unittest {
134     import std.string;
135     import std.algorithm.comparison;
136     import std.stdio;
137 
138     auto s0 = "abc";
139     InputRangeAdapter ira = makeAdapter(s0);
140     assert(ira.equal(["abc"]));
141 
142     auto s1 = "abc".representation();
143     ira = makeAdapter(s1);
144     assert(ira.equal(["abc".representation()]));
145 
146     auto s2 = ["abc".representation, "кококо".representation];
147     ira = makeAdapter(s2);
148     assert(ira.equal(s2));
149 
150     auto f = File("README.md", "r");
151     auto r = f.byLine();
152     ira = makeAdapter(r);
153 }