Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
ServiceDescription.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
8#include "Stroika/Foundation/DataExchange/ObjectVariantMapper.h"
14
15#include "ServiceDescription.h"
16
17using std::byte;
18
19using namespace Stroika::Foundation;
22
23using Memory::MakeSharedPtr;
24
25using namespace Stroika::Frameworks;
26using namespace Stroika::Frameworks::UPnP;
27
29
30namespace {
31 // the namespace every element of an SCPD is in (UPnP Device Architecture 1.1, section 2.5)
32 const IO::Network::URI kServiceNamespace_{"urn:schemas-upnp-org:service-1-0"sv};
33
35
36 // a list UPnP's schema lets be missing, but not be empty - at least one item where there is one - is left out when it has none
37 template <typename CLASS>
38 function<void (const ObjectVariantMapper&, const CLASS*, VariantValue*)> LeaveOutIfEmpty_ (const String& member)
39 {
40 return [member] (const ObjectVariantMapper&, const CLASS*, VariantValue* v) {
41 VVMap_ m = v->As<VVMap_> ();
42 if (optional<VariantValue> list = m.Lookup (member); list and list->As<Containers::Sequence<VariantValue>> ().empty ()) {
43 m.Remove (member);
44 *v = VariantValue{m};
45 }
46 };
47 }
48
49 // a ServiceDescription's fields, by UPnP's names - and UPnP's spellings, and what its schema requires, each where it is added
50 const ObjectVariantMapper& Mapper_ ()
51 {
52 static const ObjectVariantMapper kMapper_ = [] () {
54 // a direction as UPnP spells it - in or out - not by the enum's own names
55 mapper.Add<SD_::Argument::Direction> (
56 [] (const ObjectVariantMapper&, const SD_::Argument::Direction* d) -> VariantValue {
57 return VariantValue{*d == SD_::Argument::Direction::eIn ? "in"sv : "out"sv};
58 },
59 [] (const ObjectVariantMapper&, const VariantValue& v, SD_::Argument::Direction* d) {
60 *d = v.As<String> ().Trim () == "out"sv ? SD_::Argument::Direction::eOut : SD_::Argument::Direction::eIn;
61 });
62 mapper.AddClass<SD_::Argument> ({
63 {"name"sv, &SD_::Argument::fName},
64 {"direction"sv, &SD_::Argument::fDirection},
65 {"retval"sv, &SD_::Argument::fRetval},
66 {"relatedStateVariable"sv, &SD_::Argument::fRelatedStateVariable},
67 });
69 // an action with no arguments has no <argumentList>: UPnP's schema has no empty one
70 mapper.AddClass<SD_::Action> ({{"name"sv, &SD_::Action::fName}, {"argumentList"sv, &SD_::Action::fArguments}},
71 {.fAfterFrom = LeaveOutIfEmpty_<SD_::Action> ("argumentList"sv)});
73 mapper.AddCommonType<optional<String>> ();
74 mapper.AddClass<SD_::AllowedValueRange> ({{"minimum"sv, &SD_::AllowedValueRange::fMinimum},
75 {"maximum"sv, &SD_::AllowedValueRange::fMaximum},
76 {"step"sv, &SD_::AllowedValueRange::fStep}});
77 mapper.AddCommonType<optional<SD_::AllowedValueRange>> ();
79 // sendEvents as UPnP spells it, yes or no - and yes, the default, read from anything but no
82 [] (const ObjectVariantMapper&, const bool* b) -> VariantValue { return VariantValue{*b ? "yes"sv : "no"sv}; }),
84 [] (const ObjectVariantMapper&, const VariantValue& v, bool* b) { *b = v.As<String> ().Trim () != "no"sv; })};
85 // a state variable with no allowed values has no <allowedValueList>, as an action with no arguments
87 {
88 {"sendEvents"sv, &SD_::StateVariable::fSendEvents, kYesNo},
89 {"name"sv, &SD_::StateVariable::fName},
90 {"dataType"sv, &SD_::StateVariable::fDataType},
91 {"defaultValue"sv, &SD_::StateVariable::fDefaultValue},
92 {"allowedValueList"sv, &SD_::StateVariable::fAllowedValues},
93 {"allowedValueRange"sv, &SD_::StateVariable::fAllowedValueRange},
94 },
95 {.fAfterFrom = LeaveOutIfEmpty_<SD_::StateVariable> ("allowedValueList"sv)});
97 // a service with no actions has no <actionList>; and every SCPD has a specVersion, which no ServiceDescription field holds
98 mapper.AddClass<SD_> ({{"actionList"sv, &SD_::fActions}, {"serviceStateTable"sv, &SD_::fStateVariables}},
99 {.fAfterFrom = [] (const ObjectVariantMapper& m, const SD_* sd, VariantValue* v) {
100 LeaveOutIfEmpty_<SD_> ("actionList"sv) (m, sd, v);
101 VVMap_ vm = v->As<VVMap_> ();
102 // 1.0, not 1.1, on purpose - @see this framework's ReadMe.md
103 vm.Add ("specVersion"sv,
104 VariantValue{VVMap_{{"major"sv, VariantValue{"1"sv}}, {"minor"sv, VariantValue{"0"sv}}}});
105 *v = VariantValue{vm};
106 }});
107 return mapper;
108 }();
109 return kMapper_;
110 }
111
112 // the SCPD's shape (UPnP Device Architecture 1.1, section 2.5): its elements' names and order, UPnP's namespace, each list
113 // and its items, sendEvents an attribute, <retval/> there or not - and what no SCPD field holds, a vendor's own elements
114 // say, skipped
115 //
116 // @todo derive this from Mapper_ (https://github.com/SophistSolutions/Stroika/issues/1230), or from UPnP's own schema
117 // (ServiceDescriptionSchema), rather than spelling it out: what only XML says - item names, sendEvents an
118 // attribute, <retval/> a presence marker, the namespace, skipping - would be all that is left
119 const Variant::XML::Binding& Binding_ ()
120 {
122 static const Binding kBinding_ = [] () {
123 constexpr auto kSkip = Binding::UndeclaredMembers::eSkip;
124 auto text = [] () { return Binding{.fType = VariantValue::eString}; };
125 auto list = [] (const Binding& item) {
126 return Binding{.fType = VariantValue::eArray,
127 .fUndeclaredMembers = Binding::UndeclaredMembers::eSkip,
128 .fItems = MakeSharedPtr<Binding> (item)};
129 };
130 Binding argument{.fName = "argument"sv,
131 .fType = VariantValue::eMap,
132 .fMembers = {{"name"sv, text ()},
133 {"direction"sv, text ()},
134 {"retval"sv, Binding{.fKind = Binding::Kind::ePresenceMarker, .fType = VariantValue::eBoolean}},
135 {"relatedStateVariable"sv, text ()}},
136 .fUndeclaredMembers = kSkip};
137 Binding action{.fName = "action"sv,
138 .fType = VariantValue::eMap,
139 .fMembers = {{"name"sv, text ()}, {"argumentList"sv, list (argument)}},
140 .fUndeclaredMembers = kSkip};
141 Binding allowedValue{.fName = "allowedValue"sv, .fType = VariantValue::eString};
142 Binding stateVariable{
143 .fName = "stateVariable"sv,
144 .fType = VariantValue::eMap,
145 .fMembers = {{"sendEvents"sv, Binding{.fKind = Binding::Kind::eAttribute, .fType = VariantValue::eString}},
146 {"name"sv, text ()},
147 {"dataType"sv, text ()},
148 {"defaultValue"sv, text ()},
149 {"allowedValueList"sv, list (allowedValue)},
150 {"allowedValueRange"sv, Binding{.fType = VariantValue::eMap,
151 .fMembers = {{"minimum"sv, text ()}, {"maximum"sv, text ()}, {"step"sv, text ()}},
152 .fUndeclaredMembers = kSkip}}},
153 .fUndeclaredMembers = kSkip};
154 Binding specVersion{.fType = VariantValue::eMap, .fMembers = {{"major"sv, text ()}, {"minor"sv, text ()}}, .fUndeclaredMembers = kSkip};
155 Binding scpd{.fName = XML::NameWithNamespace{kServiceNamespace_, "scpd"sv},
156 .fType = VariantValue::eMap,
157 .fMembers = {{"specVersion"sv, specVersion}, {"actionList"sv, list (action)}, {"serviceStateTable"sv, list (stateVariable)}},
158 .fUndeclaredMembers = kSkip};
159 Ensure (scpd.IsComplete ()); // so what Serialize writes, DeSerialize reads back exactly
160 return scpd;
161 }();
162 return kBinding_;
163 }
164}
165
166/*
167 ********************************************************************************
168 ********************* UPnP::ServiceDescription::Argument ***********************
169 ********************************************************************************
170 */
172{
173 StringBuilder sb;
174 sb << "{"sv;
175 sb << "Name: "sv << fName;
176 sb << ", Direction: "sv << (fDirection == Direction::eIn ? "in"sv : "out"sv);
177 if (fRetval) {
178 sb << ", Retval"sv;
179 }
180 sb << ", Related-State-Variable: "sv << fRelatedStateVariable;
181 sb << "}"sv;
182 return sb;
183}
184
185/*
186 ********************************************************************************
187 ********************** UPnP::ServiceDescription::Action ************************
188 ********************************************************************************
189 */
191{
192 StringBuilder sb;
193 sb << "{"sv;
194 sb << "Name: "sv << fName;
195 sb << ", Arguments: "sv << fArguments;
196 sb << "}"sv;
197 return sb;
198}
199
200/*
201 ********************************************************************************
202 **************** UPnP::ServiceDescription::AllowedValueRange *******************
203 ********************************************************************************
204 */
206{
207 StringBuilder sb;
208 sb << "{"sv;
209 sb << "Minimum: "sv << fMinimum;
210 sb << ", Maximum: "sv << fMaximum;
211 if (fStep) {
212 sb << ", Step: "sv << *fStep;
213 }
214 sb << "}"sv;
215 return sb;
216}
217
218/*
219 ********************************************************************************
220 ******************* UPnP::ServiceDescription::StateVariable ********************
221 ********************************************************************************
222 */
224{
225 StringBuilder sb;
226 sb << "{"sv;
227 sb << "Name: "sv << fName;
228 sb << ", Data-Type: "sv << fDataType;
229 if (fDefaultValue) {
230 sb << ", Default-Value: "sv << *fDefaultValue;
231 }
232 sb << ", Send-Events: "sv << fSendEvents;
233 if (not fAllowedValues.empty ()) {
234 sb << ", Allowed-Values: "sv << fAllowedValues;
235 }
236 if (fAllowedValueRange) {
237 sb << ", Allowed-Value-Range: "sv << *fAllowedValueRange;
238 }
239 sb << "}"sv;
240 return sb;
241}
242
243/*
244 ********************************************************************************
245 ************************** UPnP::ServiceDescription ****************************
246 ********************************************************************************
247 */
249{
250 StringBuilder sb;
251 sb << "{"sv;
252 sb << "Actions: "sv << fActions;
253 sb << ", State-Variables: "sv << fStateVariables;
254 sb << "}"sv;
255 return sb;
256}
257
258/*
259 ********************************************************************************
260 ********************************* UPnP::Serialize ******************************
261 ********************************************************************************
262 */
263Memory::BLOB UPnP::Serialize (const ServiceDescription& sd, [[maybe_unused]] const ServiceDescription::Options& options)
264{
265 // the Writer writes the text itself, with no XML provider - options' provider only checks it
266 static const Variant::XML::Writer kWriter_{{.fBinding = Binding_ ()}};
267 Memory::BLOB result = kWriter_.WriteAsBLOB (Mapper_ ().FromObject (sd));
268#if qStroika_Foundation_DataExchange_XML_SupportParsing and qStroika_Foundation_DataExchange_XML_SupportSchema and qStroika_Foundation_DataExchange_XML_SupportDOM
269 if (options.fValidateWrite) {
270 XML::SAXParse (result.As<Streams::InputStream::Ptr<byte>> (), nullptr, ServiceDescriptionSchema (options.fXMLOptions), nullptr,
271 options.fXMLOptions);
272 }
273#endif
274 return result;
275}
276
277/*
278 ********************************************************************************
279 ******************************* UPnP::DeSerialize ******************************
280 ********************************************************************************
281 */
282void UPnP::DeSerialize (const Memory::BLOB& b, ServiceDescription* sd, const ServiceDescription::Options& options)
283{
284 RequireNotNull (sd);
285 // leniently: devices in the field are loose (@see DeviceDescription::Options::fValidateReadAgainst)
286 const Variant::XML::Reader reader{
287 {.fBinding = Binding_ (), .fXMLOptions = options.fXMLOptions, .fValidateAgainst = options.fValidateReadAgainst, .fLenient = true}};
288 *sd = Mapper_ ().ToObject<ServiceDescription> (reader.Read (b.As<Streams::InputStream::Ptr<byte>> ()));
289}
#define RequireNotNull(p)
Definition Assertions.h:350
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual CONTAINER_OF_Key_T As() const
A generalization of a vector: a container whose elements are keyed by the natural numbers.
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
function< VariantValue(const ObjectVariantMapper &mapper, const T *objOfType)> FromObjectMapperType
nonvirtual void AddCommonType(ARGS &&... args)
function< void(const ObjectVariantMapper &mapper, const VariantValue &d, T *into)> ToObjectMapperType
Read XML as a VariantValue - the inverse of Variant::XML::Writer, whose documentation has the shape.
Write a VariantValue as XML - an object's member is an element, an array is repeated elements.
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
How a VariantValue is to be written as XML, and read back, where the default shape (.
Definition Binding.h:88
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
Definition Namespace.h:63
What Serialize and DeSerialize of an SCPD are told - defaulted, so a caller says only what it wants d...
Foundation::DataExchange::XML::Schema::Ptr fValidateReadAgainst
Check an SCPD being read against this schema - ServiceDescriptionSchema - throwing where it does not ...
Foundation::DataExchange::XML::Options fXMLOptions
The XML provider that reads, and that checks what is written - by default, Stroika's default provider...
A service's description - its SCPD (UPnP Device Architecture 1.1, section 2.5): its actions,...