1 /**
2 License:
3     Boost Software License - Version 1.0 - August 17th, 2003
4 
5     Permission is hereby granted, free of charge, to any person or organization
6     obtaining a copy of the software and accompanying documentation covered by
7     this license (the "Software") to use, reproduce, display, distribute,
8     execute, and transmit the Software, and to prepare derivative works of the
9     Software, and to permit third-parties to whom the Software is furnished to
10     do so, all subject to the following:
11 
12     The copyright notices in the Software and this entire statement, including
13     the above license grant, this restriction and the following disclaimer,
14     must be included in all copies of the Software, in whole or in part, and
15     all derivative works of the Software, unless such copies or derivative
16     works are solely in the form of machine-executable object code generated by
17     a source language processor.
18 
19     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21     FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25     DEALINGS IN THE SOFTWARE.
26 
27 Authors:
28     Alexandru Ermicioi
29 **/
30 module aermicioi.aedi_property_reader.json.accessor;
31 
32 import aermicioi.aedi.configurer.annotation.annotation;
33 import aermicioi.aedi_property_reader.convertor.accessor;
34 import aermicioi.aedi_property_reader.convertor.exception : NotFoundException;
35 import std.json;
36 import std.exception;
37 import std.conv : text, to;
38 import std.experimental.allocator;
39 import std.experimental.logger;
40 import aermicioi.aedi_property_reader.convertor.traits : n;
41 
42 @component
43 @qualifier!(PropertyAccessor!JSONValue)
44 auto jsonPropertyAccessor(JsonPropertyAccessor propertyAccessor, JsonIndexAccessor propertyIndexer) {
45     return dsl(propertyAccessor, propertyIndexer);
46 }
47 
48 @component
49 auto runtimeJsonPropertyAccessor(PropertyAccessor!JSONValue accessor) {
50     return new WrappingFieldAccessor!JSONValue(accessor);
51 }
52 
53 /**
54 Accessor allowing access through JSONValues that are associative arrays
55 **/
56 @component
57 class JsonPropertyAccessor : PropertyAccessor!JSONValue {
58 
59     /**
60      Get a property out of component
61 
62      Params:
63          component = a component which has some properties identified by property.
64      Throws:
65          NotFoundException in case when no requested property is available.
66          InvalidArgumentException in case when passed arguments are somehow invalid for use.
67      Returns:
68          FieldType accessed property.
69      **/
70     JSONValue access(JSONValue component, in string property, RCIAllocator allocator = theAllocator) const {
71 
72         if (this.has(component, property)) {
73             return component.object[property];
74         }
75 
76         throw new NotFoundException(text("${component} doesn't have ${identity}"), property, component.to!string);
77     }
78 
79     /**
80      Check if requested property is present in component.
81 
82      Check if requested property is present in component.
83      The method could have allocation side effects due to the fact that
84      it is not restricted in calling access method of the accessor.
85 
86      Params:
87          component = component which is supposed to have property
88          property = speculated property that is to be tested if it is present in component
89      Returns:
90          true if property is in component
91      **/
92     bool has(in JSONValue component, in string property, RCIAllocator allocator = theAllocator) const nothrow {
93 
94         try {
95 
96             return (component.type == JSON_TYPE.OBJECT) && ((property in component.object) !is null);
97         } catch (Exception e) {
98 
99             debug(trace) error("Failed to check property ", property, " existence in json ", component).n;
100         }
101 
102         return false;
103     }
104 
105     /**
106      Identify the type of supported component.
107 
108      Identify the type of supported component. It returns type info of component
109      if it is supported by accessor, otherwise it will return typeid(void) denoting that
110      the type isn't supported by accessor. The accessor is not limited to returning the type
111      info of passed component, it can actually return type info of super type or any type
112      given the returned type is implicitly convertible or castable to ComponentType.
113 
114      Params:
115          component = the component for which accessor should identify the underlying type
116 
117      Returns:
118          TypeInfo type information about passed component, or typeid(void) if component is not supported.
119      **/
120     TypeInfo componentType(JSONValue component) const nothrow {
121         return typeid(JSONValue);
122     }
123 
124 }
125 
126 /**
127 Accessor allowing access to properties in a JSONValue array
128 **/
129 @component
130 class JsonIndexAccessor : PropertyAccessor!JSONValue {
131 
132     /**
133      Get a property out of component
134 
135      Params:
136          component = a component which has some properties identified by property.
137      Throws:
138          NotFoundException in case when no requested property is available.
139          InvalidArgumentException in case when passed arguments are somehow invalid for use.
140      Returns:
141          FieldType accessed property.
142      **/
143     JSONValue access(JSONValue component, in string property, RCIAllocator allocator = theAllocator) const {
144 
145         if (this.has(component, property)) {
146             import std.conv : to;
147 
148             return component.array[property.to!size_t];
149         }
150 
151         throw new NotFoundException(text("${component} doesn't have child on index ${identity}"), property.to!string, component.to!string);
152     }
153 
154     /**
155      Check if requested property is present in component.
156 
157      Check if requested property is present in component.
158      The method could have allocation side effects due to the fact that
159      it is not restricted in calling access method of the accessor.
160 
161      Params:
162          component = component which is supposed to have property
163          property = speculated property that is to be tested if it is present in component
164      Returns:
165          true if property is in component
166      **/
167     bool has(in JSONValue component, in string property, RCIAllocator allocator = theAllocator) const nothrow {
168 
169         try {
170             import std..string : isNumeric;
171             import std.conv : to;
172 
173             return (component.type == JSON_TYPE.ARRAY) &&
174                 property.isNumeric && (component.array.length > property.to!size_t);
175         } catch (Exception e) {
176 
177             debug(trace) error("Failed to check indexed property ", property, " existence in json ", component).n;
178         }
179 
180         return false;
181     }
182 
183     /**
184      Identify the type of supported component.
185 
186      Identify the type of supported component. It returns type info of component
187      if it is supported by accessor, otherwise it will return typeid(void) denoting that
188      the type isn't supported by accessor. The accessor is not limited to returning the type
189      info of passed component, it can actually return type info of super type or any type
190      given the returned type is implicitly convertible or castable to ComponentType.
191 
192      Params:
193          component = the component for which accessor should identify the underlying type
194 
195      Returns:
196          TypeInfo type information about passed component, or typeid(void) if component is not supported.
197      **/
198     TypeInfo componentType(JSONValue component) const nothrow {
199         return typeid(JSONValue);
200     }
201 }