From ad609e420c384ac8d8e4d8d8e5bf5a8318901312 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 12 Mar 2020 19:49:31 -0700 Subject: [PATCH] client Add NTURI version of rpc() and consolidate with similar code for building pvRequest _options and put() builder. --- src/clientget.cpp | 139 ++++++++++++++++++++++++++------------------- src/clientmon.cpp | 2 +- src/clientreq.cpp | 44 ++++++-------- src/pvxs/client.h | 87 ++++++++++++++++++++-------- test/testpvreq.cpp | 82 ++++++++++++++++++++++++-- test/testrpc.cpp | 22 ++++++- 6 files changed, 262 insertions(+), 114 deletions(-) diff --git a/src/clientget.cpp b/src/clientget.cpp index b28a510..44a940b 100644 --- a/src/clientget.cpp +++ b/src/clientget.cpp @@ -1,12 +1,12 @@ -/** +/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvxs is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ - #include #include +#include #include "clientimpl.h" namespace pvxs { @@ -15,24 +15,23 @@ namespace client { DEFINE_LOGGER(setup, "pvxs.client.setup"); DEFINE_LOGGER(io, "pvxs.client.io"); -struct PutBuilder::Pvt +namespace detail { + +struct PRBase::Args { - struct FieldValue { - impl::FieldStorage value; - TypeCode code = TypeCode::Null; - bool required; - }; + std::map> values; + std::vector names; - std::map> values; - - Value build(Value&& prototype) + // put() builder + Value build(Value&& prototype) const { Value ret(prototype.cloneEmpty()); for(auto& pair : values) { if(auto fld = ret[pair.first]) { try { - fld.copyIn(static_cast(&pair.second.first.store), pair.second.first.code); + auto store = Value::Helper::store(pair.second.first); + fld.copyIn(static_cast(&store->store), store->code); }catch(NoConvert& e){ if(pair.second.second) throw; @@ -44,50 +43,66 @@ struct PutBuilder::Pvt } return ret; } + + Value uriArgs() const + { + TypeDef type(nt::NTURI{}.build()); + + std::list arguments; + + for(auto& name : names) { + auto it = values.find(name); + if(it==values.end()) + throw std::logic_error("uriArgs() names vs. values mis-match"); + + auto& value = it->second.first; + + arguments.push_back(TypeDef(value).as(name)); + } + + type += {Member(TypeCode::Struct, "query", arguments)}; + + auto inst(type.create()); + + for(auto& pair : values) { + inst["query"][pair.first].assign(pair.second.first); + } + + return inst; + } }; -PutBuilder& PutBuilder::set(const std::string& name, const void *ptr, StoreType type, bool required) +PRBase::~PRBase() {} + +Value PRBase::_builder(Value&& prototype) const { - if(!pvt) - pvt = std::make_shared(); - - if(pvt->values.find(name)!=pvt->values.end()) - throw std::logic_error(SB()<<"PutBuilder can't assign a second value to field '"<values[name]; - pair.second = required; - - pair.first.init(type); - switch(type) { - case StoreType::Bool: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::Real: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::Integer: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::UInteger: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::String: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::Array: - pair.first.as() = *static_cast(ptr); - break; - case StoreType::Compound: - pair.first.as() = *static_cast(ptr); - break; - default: - throw std::logic_error("PutBuilder::set() currently only supports scalar types"); - } - - return *this; + assert(_args); + return _args->build(std::move(prototype)); } -PutBuilder::~PutBuilder() {} +Value PRBase::_uriArgs() const +{ + assert(_args); + return _args->uriArgs(); +} + +void PRBase::_set(const std::string& name, const void *ptr, StoreType type, bool required) +{ + if(!_args) + _args = std::make_shared(); + + if(_args->values.find(name)!=_args->values.end()) + throw std::logic_error(SB()<<"PutBuilder can't assign a second value to field '"<values.emplace(std::piecewise_construct, + std::make_tuple(name), + std::make_tuple(std::move(aval), required)); + _args->names.push_back(name); +} + +} // namespace detail namespace { @@ -442,7 +457,7 @@ std::shared_ptr GetBuilder::_exec_get() auto op = std::make_shared(Operation::Get, chan); op->setDone(std::move(_result)); - op->pvRequest = _build(); + op->pvRequest = _buildReq(); chan->pending.push_back(op); chan->createOperations(); @@ -457,7 +472,7 @@ std::shared_ptr PutBuilder::exec() { std::shared_ptr ret; - if(!_builder && !pvt) + if(!_builder && !_args) throw std::logic_error("put() needs either a .build() or at least one .set()"); ctx->tcp_loop.call([&ret, this]() { @@ -468,8 +483,8 @@ std::shared_ptr PutBuilder::exec() if(_builder) { op->builder = std::move(_builder); - } else if(pvt) { - auto build = std::move(pvt); + } else if(_args) { + auto build = std::move(_args); op->builder = [build](Value&& prototype) -> Value { return build->build(std::move(prototype)); }; @@ -477,7 +492,7 @@ std::shared_ptr PutBuilder::exec() // handled above } op->getOput = _doGet; - op->pvRequest = _build(); + op->pvRequest = _buildReq(); chan->pending.push_back(op); chan->createOperations(); @@ -492,13 +507,21 @@ std::shared_ptr RPCBuilder::exec() { std::shared_ptr ret; + if(_args && _argument) + throw std::logic_error("Use of rpc() with argument and builder .arg() are mutually exclusive"); + ctx->tcp_loop.call([&ret, this]() { auto chan = Channel::build(ctx, _name); auto op = std::make_shared(Operation::RPC, chan); op->setDone(std::move(_result)); - op->rpcarg = std::move(_argument); - op->pvRequest = _build(); + if(_argument) { + op->rpcarg = std::move(_argument); + } else if(_args) { + op->rpcarg = _args->uriArgs(); + op->rpcarg["path"] = _name; + } + op->pvRequest = _buildReq(); chan->pending.push_back(op); chan->createOperations(); diff --git a/src/clientmon.cpp b/src/clientmon.cpp index 7547794..3a542ad 100644 --- a/src/clientmon.cpp +++ b/src/clientmon.cpp @@ -512,7 +512,7 @@ std::shared_ptr MonitorBuilder::exec() auto op = std::make_shared(Operation::Monitor, chan); op->event = std::move(_event); - op->pvRequest = _build(); + op->pvRequest = _buildReq(); op->maskConn = _maskConn; op->maskDiscon = _maskDisconn; diff --git a/src/clientreq.cpp b/src/clientreq.cpp index 58be389..376ac53 100644 --- a/src/clientreq.cpp +++ b/src/clientreq.cpp @@ -10,6 +10,7 @@ #include #include +#include "dataimpl.h" #include "utilpvt.h" namespace pvxs { @@ -22,13 +23,9 @@ struct CommonBase::Req { Member fields; std::map options; - Member record; Req() :fields(TypeCode::Struct, "field") - ,record(TypeCode::Struct, "record", { - Member(TypeCode::Struct, "_options") - }) {} }; @@ -90,25 +87,7 @@ void CommonBase::_record(const std::string& key, const void* value, StoreType vt if(!req) req = std::make_shared(); - TypeCode base; - switch (vtype) { - case StoreType::Bool: base = TypeCode::Bool; break; - case StoreType::Integer: base = TypeCode::Int64; break; - case StoreType::UInteger: base = TypeCode::UInt64; break; - case StoreType::Real: base = TypeCode::Float64; break; - case StoreType::String: base = TypeCode::String; break; - default: - throw std::logic_error("record() only support scalar values"); - } - - Value v = TypeDef(base).create(); - v.copyIn(value, vtype); - - if(req->options.find(key)==req->options.end()) { - req->record.children[0].addChild(Member(base, key)); - } - - req->options[key] = std::move(v); + req->options[key] = Value::Helper::build(value, vtype); } struct PVRParser @@ -309,7 +288,7 @@ void CommonBase::_parse(const std::string& req) PVRParser(*this, req.c_str()).parse(); } -Value CommonBase::_build() const +Value CommonBase::_buildReq() const { if(!req) { using namespace pvxs::members; @@ -321,10 +300,21 @@ Value CommonBase::_build() const return req->pvRequest; } else { - auto inst = TypeDef(TypeCode::Struct, { + using namespace pvxs::members; + + auto def = TypeDef(TypeCode::Struct, { req->fields, - req->record, - }).create(); + }); + + { + std::vector opts; + for(auto& pair : req->options) { + opts.push_back(TypeDef(pair.second).as(pair.first)); + } + def += {Struct("record", {Struct("_options", opts)})}; + } + + auto inst = def.create(); auto opt = inst["record._options"]; for(auto& pair : req->options) { diff --git a/src/pvxs/client.h b/src/pvxs/client.h index e7a343a..bdfe3d5 100644 --- a/src/pvxs/client.h +++ b/src/pvxs/client.h @@ -180,7 +180,7 @@ struct PVXS_API Subscription { }; class GetBuilder; -struct PutBuilder; +class PutBuilder; class RPCBuilder; class MonitorBuilder; @@ -289,6 +289,9 @@ public: inline PutBuilder put(const std::string& pvname); + inline + RPCBuilder rpc(const std::string& pvname); + /** Execute "stateless" remote procedure call operation. * * @code @@ -364,21 +367,34 @@ protected: void _field(const std::string& s); void _record(const std::string& key, const void* value, StoreType vtype); void _parse(const std::string& req); - Value _build() const; + Value _buildReq() const; friend struct PVRParser; }; -//! Options common to all operations -template -class CommonBuilder : public CommonBase { +class PVXS_API PRBase : public CommonBase { protected: - constexpr CommonBuilder(const std::shared_ptr& ctx, const std::string& name) : CommonBase(ctx, name) {} + struct Args; + std::shared_ptr _args; + + PRBase(const std::shared_ptr& ctx, const std::string& name) : CommonBase(ctx, name) {} + ~PRBase(); + + void _set(const std::string& name, const void *ptr, StoreType type, bool required); + Value _builder(Value&& prototype) const; + Value _uriArgs() const; +}; + +//! Options common to all operations +template +class CommonBuilder : public Base { +protected: + constexpr CommonBuilder(const std::shared_ptr& ctx, const std::string& name) : Base(ctx, name) {} inline SubBuilder& _sb() { return static_cast(*this); } public: //! Add field to pvRequest blob. //! A more efficient alternative to @code pvRequest("field(name)") @endcode - SubBuilder& field(const std::string& fld) { _field(fld); return _sb(); } + SubBuilder& field(const std::string& fld) { this->_field(fld); return _sb(); } /** Add a key/value option to the request. * @@ -395,7 +411,7 @@ public: SubBuilder& record(const std::string& name, const T& val) { typedef impl::StorageMap::type> map_t; typename map_t::store_t norm(val); - _record(name, &norm, map_t::code); + this->_record(name, &norm, map_t::code); return _sb(); } @@ -407,19 +423,19 @@ public: * - field() * - record(=\) */ - SubBuilder& pvRequest(const std::string& expr) { _parse(expr); return _sb(); } + SubBuilder& pvRequest(const std::string& expr) { this->_parse(expr); return _sb(); } //! Store raw pvRequest blob. - SubBuilder& rawRequest(Value&& r) { _rawRequest(std::move(r)); return _sb(); } + SubBuilder& rawRequest(Value&& r) { this->_rawRequest(std::move(r)); return _sb(); } - SubBuilder& priority(int p) { _prio = p; return _sb(); } - SubBuilder& server(const std::string& s) { _server = s; return _sb(); } + SubBuilder& priority(int p) { this->_prio = p; return _sb(); } + SubBuilder& server(const std::string& s) { this->_server = s; return _sb(); } }; } // namespace detail //! Prepare a remote GET or GET_FIELD (info) operation. -class GetBuilder : public detail::CommonBuilder { +class GetBuilder : public detail::CommonBuilder { std::function _result; bool _get; PVXS_API @@ -446,16 +462,12 @@ GetBuilder Context::info(const std::string& name) { return GetBuilder{pvt, name, GetBuilder Context::get(const std::string& name) { return GetBuilder{pvt, name, true}; } //! Prepare a remote PUT operation -struct PVXS_API PutBuilder : public detail::CommonBuilder { - struct Pvt; -private: +class PutBuilder : public detail::CommonBuilder { bool _doGet = true; std::function _builder; std::function _result; - std::shared_ptr pvt; public: PutBuilder(const std::shared_ptr& ctx, const std::string& name) :CommonBuilder{ctx,name} {} - ~PutBuilder(); /** If fetchPresent is true (the default). Then the Value passed to * the build() callback will be initialized with a previous value for this PV. @@ -466,7 +478,10 @@ public: */ PutBuilder& fetchPresent(bool f) { _doGet = f; return *this; } - PutBuilder& set(const std::string& name, const void *ptr, StoreType type, bool required); + PutBuilder& set(const std::string& name, const void *ptr, StoreType type, bool required) { + _set(name, ptr, type, required); + return *this; + } /** Utilize default .build() to assign a value to the named field. * @@ -504,6 +519,7 @@ public: * The caller must keep returned Operation pointer until completion * or the operation will be implicitly canceled. */ + PVXS_API std::shared_ptr exec(); friend struct Context::Pvt; @@ -511,15 +527,35 @@ public: PutBuilder Context::put(const std::string& name) { return PutBuilder{pvt, name}; } //! Prepare a remote RPC operation -class RPCBuilder : public detail::CommonBuilder { +class RPCBuilder : public detail::CommonBuilder { Value _argument; std::function _result; + friend class Context; public: - RPCBuilder(const std::shared_ptr& ctx, const std::string& name, Value&& arg) :CommonBuilder{ctx,name}, _argument(std::move(arg)) {} + RPCBuilder(const std::shared_ptr& ctx, const std::string& name) :CommonBuilder{ctx,name} {} //! Callback through which result Value or an error will be delivered. //! The functor is stored in the Operation returned by exec(). RPCBuilder& result(std::function&& cb) { _result = std::move(cb); return *this; } + RPCBuilder& arg(const std::string& name, const void *ptr, StoreType type) { + _set(name, ptr, type, true); + return *this; + } + + /** Provide argument value. + * + * @param name Argument name + * @param val The value to assign. cf. Value::from() + */ + template + RPCBuilder& arg(const std::string& name, const T& val) + { + typedef impl::StorageMap::type> map_t; + typename map_t::store_t norm(val); + _set(name, &norm, map_t::code, true); + return *this; + } + /** Execute the network operation. * The caller must keep returned Operation pointer until completion * or the operation will be implicitly canceled. @@ -529,10 +565,15 @@ public: friend struct Context::Pvt; }; -RPCBuilder Context::rpc(const std::string& name, Value&& arg) { return RPCBuilder{pvt, name, std::move(arg)}; } +RPCBuilder Context::rpc(const std::string& name) { return RPCBuilder{pvt, name}; } +RPCBuilder Context::rpc(const std::string& name, Value&& arg) { + RPCBuilder ret{pvt, name}; + ret._argument = std::move(arg); + return ret; +} //! Prepare a remote subscription -class MonitorBuilder : public detail::CommonBuilder { +class MonitorBuilder : public detail::CommonBuilder { std::function _event; bool _maskConn = true; bool _maskDisconn = false; diff --git a/test/testpvreq.cpp b/test/testpvreq.cpp index f6290a7..f20ed5a 100644 --- a/test/testpvreq.cpp +++ b/test/testpvreq.cpp @@ -14,20 +14,38 @@ #include #include +#include #include +#include #include "utilpvt.h" namespace { using namespace pvxs; -struct TestBuilder : client::detail::CommonBuilder +struct TestBuilder : client::detail::CommonBuilder { TestBuilder() - :client::detail::CommonBuilder(nullptr, "") + :client::detail::CommonBuilder(nullptr, "") {} Value makeReq() const { - return _build(); + return _buildReq(); + } + + template + TestBuilder& set(const std::string& name, const T& val, bool required=true) { + typedef impl::StorageMap::type> map_t; + typename map_t::store_t norm(val); + _set(name, &norm, map_t::code, required); + return *this; + } + + Value builder(Value&& prototype) { + return _builder(std::move(prototype)); + } + + Value uriArgs() { + return _uriArgs(); } }; @@ -189,11 +207,65 @@ void testError() } } +void testBuilder() +{ + testShow()<<__func__; + + auto builder = TestBuilder() + .set("value", "14") + .set("alarm.severity", 3) + .set("alarm", 42, false) + .set("nonexistant", 42, false); + + auto built = builder.builder(nt::NTScalar{TypeCode::UInt32}.create()); + + testEq(built["value"].as(), 14u); + testEq(built["alarm.severity"].as(), 3u); +} + +void testArgs() +{ + using namespace pvxs::members; + + testShow()<<__func__; + + shared_array iarr({1,2,3}); + + auto sub = TypeDef(TypeCode::Struct, { + Int32("ival") + }).create(); + + sub["ival"] = 123; + + auto args = TestBuilder() + .set("a", "14") + .set("b", 3) + .set("c", iarr.freeze().castTo()) + .set("d", sub) + .uriArgs(); + + testEq(std::string(SB()<<"\n"<wait(); + + testEq(result["query.a"].as(), 5); + testEq(result["query.b"].as(), "hello"); + } }; } // namespace MAIN(testrpc) { - testPlan(14); + testPlan(16); Tester().echo(); Tester().lazy(); Tester().null(); Tester().timeout(); Tester().cancel(); Tester().error(); + Tester().builder(); cleanup_for_valgrind(); return testDone(); }