diff --git a/languages/cpp/src/shared/src/Accessor/Accessor.h b/languages/cpp/src/shared/src/Accessor/Accessor.h index 29dd7dda..5441368b 100644 --- a/languages/cpp/src/shared/src/Accessor/Accessor.h +++ b/languages/cpp/src/shared/src/Accessor/Accessor.h @@ -21,6 +21,7 @@ #include "Module.h" #include "WorkerPool.h" #include "Transport/Transport.h" +#include "Async/Async.h" #include "Event/Event.h" #include "Logger/Logger.h" @@ -108,6 +109,7 @@ namespace FireboltSDK { { Firebolt::Error status = CreateTransport(_config.WsUrl.Value().c_str(), listener, _config.WaitTime.Value()); if (status == Firebolt::Error::None) { + Async::Instance().Configure(_transport); status = CreateEventHandler(); } return status; @@ -118,6 +120,7 @@ namespace FireboltSDK { Firebolt::Error status = Firebolt::Error::None; status = DestroyTransport(); if (status == Firebolt::Error::None) { + Async::Dispose(); status = DestroyEventHandler(); } return status; diff --git a/languages/cpp/src/shared/src/Async/Async.cpp b/languages/cpp/src/shared/src/Async/Async.cpp new file mode 100644 index 00000000..f9994d7d --- /dev/null +++ b/languages/cpp/src/shared/src/Async/Async.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "Transport/Transport.h" +#include "Async.h" + +namespace FireboltSDK { + Async* Async::_singleton = nullptr; + Async::Async() + : _methodMap() + , _adminLock() + , _transport(nullptr) + { + ASSERT(_singleton == nullptr); + _singleton = this; + } + + Async::~Async() /* override */ + { + Clear(); + _transport = nullptr; + _singleton = nullptr; + } + + /* static */ Async& Async::Instance() + { + static Async *instance = new Async(); + ASSERT(instance != nullptr); + return *instance; + } + + /* static */ void Async::Dispose() + { + ASSERT(_singleton != nullptr); + + if (_singleton != nullptr) { + delete _singleton; + } + } + + void Async::Configure(Transport* transport) + { + _transport = transport; + } + + void Async::Clear() + { + _adminLock.Lock(); + MethodMap::iterator index = _methodMap.begin(); + while (index != _methodMap.end()) { + CallbackMap::iterator callbackIndex = index->second.begin(); + while (callbackIndex != index->second.end()) { + if (IsValidJob(callbackIndex->second)) { + WPEFramework::Core::IWorkerPool::Instance().Revoke(callbackIndex->second.job); + } + callbackIndex = index->second.erase(callbackIndex); + } + index = _methodMap.erase(index); + } + _adminLock.Unlock(); + } +} + diff --git a/languages/cpp/src/shared/src/Async/Async.h b/languages/cpp/src/shared/src/Async/Async.h new file mode 100644 index 00000000..a141b9e5 --- /dev/null +++ b/languages/cpp/src/shared/src/Async/Async.h @@ -0,0 +1,203 @@ +/* + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "Module.h" + +namespace FireboltSDK { + + class Async { + private: + Async(); + public: + virtual ~Async(); + Async(const Async&) = delete; + Async& operator= (const Async&) = delete; + + public: + typedef std::function DispatchFunction; + + class Job : public WPEFramework::Core::IDispatch { + protected: + Job(Async& parent, const string& method, const DispatchFunction lambda, void* usercb) + : _parent(parent) + , _method(method) + , _lambda(lambda) + , _usercb(usercb) + { + } + + public: + Job() = delete; + Job(const Job&) = delete; + Job& operator=(const Job&) = delete; + + ~Job() = default; + + public: + static WPEFramework::Core::ProxyType Create(Async& parent, const string& method, const DispatchFunction lambda, void* usercb); + + void Dispatch() override + { + _lambda(_parent, _usercb); + } + + private: + Async& _parent; + string _method; + DispatchFunction _lambda; + void* _usercb; + }; + + public: + struct CallbackData { + const DispatchFunction lambda; + WPEFramework::Core::ProxyType job; + uint32_t id; + }; + + using CallbackMap = std::map; + using MethodMap = std::map; + + private: + static constexpr uint32_t DefaultId = 0xFFFFFFFF; + static constexpr uint32_t DefaultWaitTime = WPEFramework::Core::infinite; + + public: + static Async& Instance(); + static void Dispose(); + void Configure(Transport* transport); + + public: + template + Firebolt::Error Invoke(const string& method, const PARAMETERS& parameters, const CALLBACK& callback, void* usercb, uint32_t waitTime = DefaultWaitTime) + { + Firebolt::Error status = Firebolt::Error::None; + if (_transport != nullptr) { + Transport* transport = _transport; + std::function actualCallback = callback; + DispatchFunction lambda = [actualCallback, transport, method, parameters, waitTime](Async& parent, void* usercb) -> Firebolt::Error { + RESPONSE response; + uint32_t id = DefaultId; + Firebolt::Error status = transport->InvokeAsync(method, parameters, id); + if (status == Firebolt::Error::None && parent.IsActive(method, usercb) == true) { + parent.UpdateEntry(method, usercb, id); + status = transport->WaitForResponse(id, response, waitTime); + if (status == Firebolt::Error::None && parent.IsActive(method, usercb) == true) { + WPEFramework::Core::ProxyType* jsonResponse = new WPEFramework::Core::ProxyType(); + *jsonResponse = WPEFramework::Core::ProxyType::Create(); + (*jsonResponse)->FromString(response); + actualCallback(usercb, jsonResponse, status); + parent.RemoveEntry(method, usercb); + } + + } + return (status); + }; + + _adminLock.Lock(); + WPEFramework::Core::ProxyType job = WPEFramework::Core::ProxyType(WPEFramework::Core::ProxyType::Create(*this, method, lambda, usercb)); + CallbackData callbackData = {lambda, job, DefaultId}; + MethodMap::iterator index = _methodMap.find(method); + if (index != _methodMap.end()) { + CallbackMap::iterator callbackIndex = index->second.find(usercb); + if (callbackIndex == index->second.end()) { + index->second.emplace(std::piecewise_construct, std::forward_as_tuple(usercb), std::forward_as_tuple(callbackData)); + } + } else { + + CallbackMap callbackMap; + callbackMap.emplace(std::piecewise_construct, std::forward_as_tuple(usercb), std::forward_as_tuple(callbackData)); + _methodMap.emplace(std::piecewise_construct, std::forward_as_tuple(method), std::forward_as_tuple(callbackMap)); + } + _adminLock.Unlock(); + + WPEFramework::Core::IWorkerPool::Instance().Submit(job); + } + + return status; + } + + Firebolt::Error Abort(const string& method, void* usercb) + { + RemoveEntry(method, usercb); + return (Firebolt::Error::None); + } + + void UpdateEntry(const string& method, void* usercb, uint32_t id) + { + _adminLock.Lock(); + MethodMap::iterator index = _methodMap.find(method); + if (index != _methodMap.end()) { + CallbackMap::iterator callbackIndex = index->second.find(usercb); + if (callbackIndex != index->second.end()) { + callbackIndex->second.id = id; + } + } + _adminLock.Unlock(); + } + + void RemoveEntry(const string& method, void* usercb) + { + _adminLock.Lock(); + MethodMap::iterator index = _methodMap.find(method); + if (index != _methodMap.end()) { + CallbackMap::iterator callbackIndex = index->second.find(usercb); + if (callbackIndex != index->second.end()) { + if (IsValidJob(callbackIndex->second)) { + WPEFramework::Core::IWorkerPool::Instance().Revoke(callbackIndex->second.job); + } + index->second.erase(callbackIndex); + if (index->second.size() == 0) { + _methodMap.erase(index); + } + } + } + _adminLock.Unlock(); + } + + bool IsActive(const string& method, void* usercb) + { + bool valid = false; + _adminLock.Lock(); + MethodMap::iterator index = _methodMap.find(method); + if (index != _methodMap.end()) { + CallbackMap::iterator callbackIndex = index->second.find(usercb); + if (callbackIndex != index->second.end()) { + valid = true; + } + } + _adminLock.Unlock(); + return valid; + } + + private: + void Clear(); + inline bool IsValidJob(CallbackData& callbackData) { + return (callbackData.job.IsValid() && (callbackData.id == DefaultId)); + } + + private: + MethodMap _methodMap; + WPEFramework::Core::CriticalSection _adminLock; + Transport* _transport; + + static Async* _singleton; + }; +} diff --git a/languages/cpp/src/shared/src/CMakeLists.txt b/languages/cpp/src/shared/src/CMakeLists.txt index bff98101..222ba35f 100644 --- a/languages/cpp/src/shared/src/CMakeLists.txt +++ b/languages/cpp/src/shared/src/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(${TARGET} ${FIREBOLT_LIBRARY_TYPE} Transport/Transport.cpp Accessor/Accessor.cpp Event/Event.cpp + Async/Async.cpp ) set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/languages/cpp/src/shared/src/Event/Event.cpp b/languages/cpp/src/shared/src/Event/Event.cpp index 2438e3c4..5d739d70 100644 --- a/languages/cpp/src/shared/src/Event/Event.cpp +++ b/languages/cpp/src/shared/src/Event/Event.cpp @@ -126,7 +126,7 @@ namespace FireboltSDK { { Firebolt::Error status = Firebolt::Error::None; _adminLock.Lock(); - EventMap::iterator eventIndex = _eventMap.find(eventName); + EventMap::iterator eventIndex = _eventMap.begin(); if (eventIndex != _eventMap.end()) { CallbackMap::iterator callbackIndex = eventIndex->second.find(usercb); if (callbackIndex->second.state != State::EXECUTING) { @@ -146,4 +146,18 @@ namespace FireboltSDK { return status; } + + void Event::Clear() + { + EventMap::iterator eventIndex = _eventMap.begin(); + while (eventIndex != _eventMap.end()) { + CallbackMap::iterator callbackIndex = eventIndex->second.begin(); + while (callbackIndex != eventIndex->second.end()) { + callbackIndex = eventIndex->second.erase(callbackIndex); + } + eventIndex = _eventMap.erase(eventIndex); + } + _adminLock.Unlock(); + } + } diff --git a/languages/cpp/src/shared/src/Event/Event.h b/languages/cpp/src/shared/src/Event/Event.h index cf2fdced..7299fde0 100644 --- a/languages/cpp/src/shared/src/Event/Event.h +++ b/languages/cpp/src/shared/src/Event/Event.h @@ -149,6 +149,7 @@ namespace FireboltSDK { Firebolt::Error Revoke(const string& eventName, void* usercb); private: + void Clear(); Firebolt::Error ValidateResponse(const WPEFramework::Core::ProxyType& jsonResponse, bool& enabled) override; Firebolt::Error Dispatch(const string& eventName, const WPEFramework::Core::ProxyType& jsonResponse) override; diff --git a/languages/cpp/src/shared/src/FireboltSDK.h b/languages/cpp/src/shared/src/FireboltSDK.h index d78687dc..3b608cce 100644 --- a/languages/cpp/src/shared/src/FireboltSDK.h +++ b/languages/cpp/src/shared/src/FireboltSDK.h @@ -21,6 +21,7 @@ #include "Transport/Transport.h" #include "Properties/Properties.h" #include "Accessor/Accessor.h" +#include "Async/Async.h" #include "Logger/Logger.h" #include "TypesPriv.h" #include "types.h" diff --git a/languages/cpp/src/shared/src/Transport/Transport.h b/languages/cpp/src/shared/src/Transport/Transport.h index e705c5c1..0caa7603 100644 --- a/languages/cpp/src/shared/src/Transport/Transport.h +++ b/languages/cpp/src/shared/src/Transport/Transport.h @@ -587,6 +587,58 @@ namespace FireboltSDK { return (result); } + template + Firebolt::Error InvokeAsync(const string& method, const PARAMETERS& parameters, uint32_t& id) + { + Entry slot; + id = _channel->Sequence(); + return Send(method, parameters, id); + } + + template + Firebolt::Error WaitForResponse(const uint32_t& id, RESPONSE& response, const uint32_t waitTime) + { + int32_t result = WPEFramework::Core::ERROR_TIMEDOUT; + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry& slot(index->second); + _adminLock.Unlock(); + + if (slot.WaitForResponse(waitTime) == true) { + WPEFramework::Core::ProxyType jsonResponse = slot.Response(); + + // See if we have a jsonResponse, maybe it was just the connection + // that closed? + if (jsonResponse.IsValid() == true) { + if (jsonResponse->Error.IsSet() == true) { + result = jsonResponse->Error.Code.Value(); + } + else { + result = WPEFramework::Core::ERROR_NONE; + if ((jsonResponse->Result.IsSet() == true) + && (jsonResponse->Result.Value().empty() == false)) { + FromMessage((INTERFACE*)&response, *jsonResponse); + } + } + } + } else { + result = WPEFramework::Core::ERROR_TIMEDOUT; + } + _adminLock.Lock(); + _pendingQueue.erase(id); + _adminLock.Unlock(); + return FireboltErrorValue(result); + } + + void Abort(uint32_t id) + { + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry& slot(index->second); + _adminLock.Unlock(); + slot.Abort(id); + } + template Firebolt::Error Subscribe(const string& eventName, const string& parameters, RESPONSE& response) { @@ -790,41 +842,6 @@ namespace FireboltSDK { return FireboltErrorValue(result); } - template - Firebolt::Error WaitForResponse(const uint32_t& id, RESPONSE& response, const uint32_t waitTime) - { - int32_t result = WPEFramework::Core::ERROR_TIMEDOUT; - _adminLock.Lock(); - typename PendingMap::iterator index = _pendingQueue.find(id); - Entry& slot(index->second); - _adminLock.Unlock(); - - if (slot.WaitForResponse(waitTime) == true) { - WPEFramework::Core::ProxyType jsonResponse = slot.Response(); - - // See if we have a jsonResponse, maybe it was just the connection - // that closed? - if (jsonResponse.IsValid() == true) { - if (jsonResponse->Error.IsSet() == true) { - result = jsonResponse->Error.Code.Value(); - } - else { - result = WPEFramework::Core::ERROR_NONE; - if ((jsonResponse->Result.IsSet() == true) - && (jsonResponse->Result.Value().empty() == false)) { - FromMessage((INTERFACE*)&response, *jsonResponse); - } - } - } - } else { - result = WPEFramework::Core::ERROR_TIMEDOUT; - } - _adminLock.Lock(); - _pendingQueue.erase(id); - _adminLock.Unlock(); - return FireboltErrorValue(result); - } - static constexpr uint32_t WAITSLOT_TIME = 100; template Firebolt::Error WaitForEventResponse(const uint32_t& id, const string& eventName, RESPONSE& response, const uint32_t waitTime) diff --git a/languages/cpp/templates/codeblocks/interface.h b/languages/cpp/templates/codeblocks/interface.h index 376426ee..eb299a9c 100644 --- a/languages/cpp/templates/codeblocks/interface.h +++ b/languages/cpp/templates/codeblocks/interface.h @@ -1,8 +1,8 @@ struct I${info.Title}Session : virtual public IFocussableProviderSession { virtual ~I${info.Title}Session() override = default; - virtual void error( ${provider.xerror.name} error, Firebolt::Error *err = nullptr ) = 0; - virtual void result( ${provider.xresponse.name} result, Firebolt::Error *err = nullptr ) = 0; + virtual void error( ${provider.xerror.name} error, Firebolt::Error *err = nullptr ) = 0; + virtual void result( ${provider.xresponse.name} result, Firebolt::Error *err = nullptr ) = 0; }; struct I${info.Title}Provider { diff --git a/languages/cpp/templates/declarations-override/x-uses.h b/languages/cpp/templates/declarations-override/x-uses.h new file mode 100644 index 00000000..e48d9e75 --- /dev/null +++ b/languages/cpp/templates/declarations-override/x-uses.h @@ -0,0 +1,6 @@ + /* + ${method.name} + ${method.description} + */ + void request${method.Name}(${method.signature.params}${if.params}, ${end.if.params}I${info.Title}AsyncResponse& response, Firebolt::Error *err = nullptr ) override; + void abort${method.Name}(I${info.Title}AsyncResponse& response, Firebolt::Error *err = nullptr) override; diff --git a/languages/cpp/templates/declarations/x-uses.h b/languages/cpp/templates/declarations/x-uses.h new file mode 100644 index 00000000..707a6ff1 --- /dev/null +++ b/languages/cpp/templates/declarations/x-uses.h @@ -0,0 +1,6 @@ + /* + ${method.name} + ${method.description} + */ + virtual void request${method.Name}(${method.signature.params}${if.params}, ${end.if.params}I${info.Title}AsyncResponse& response, Firebolt::Error *err = nullptr ) = 0; + virtual void abort${method.Name}(I${info.Title}AsyncResponse& response, Firebolt::Error *err = nullptr) = 0; diff --git a/languages/cpp/templates/interfaces/focusable.cpp b/languages/cpp/templates/interfaces/focusable.cpp index 9a8d859d..caf7ba46 100644 --- a/languages/cpp/templates/interfaces/focusable.cpp +++ b/languages/cpp/templates/interfaces/focusable.cpp @@ -13,11 +13,11 @@ { ProviderFocusSession("${info.title.lowercase}.${method.name}Focus", _correlationId, err); } - void result( ${provider.xresponse.name} response, Firebolt::Error *err = nullptr ) override + void result( ${provider.xresponse.name} response, Firebolt::Error *err = nullptr ) override { ProviderResultSession("${info.title.lowercase}.${method.name}Response", _correlationId, response, err); } - void error( ${provider.xerror.name} error, Firebolt::Error *err = nullptr ) override + void error( ${provider.xerror.name} error, Firebolt::Error *err = nullptr ) override { ProviderErrorSession("${info.title.lowercase}.${method.name}Error", _correlationId, error, err); } diff --git a/languages/cpp/templates/methods/x-uses.cpp b/languages/cpp/templates/methods/x-uses.cpp new file mode 100644 index 00000000..d990ba96 --- /dev/null +++ b/languages/cpp/templates/methods/x-uses.cpp @@ -0,0 +1,45 @@ + + /* ${method.name}AsyncResponseInnerCallback */ + static void ${method.name}AsyncResponseInnerCallback(void* notification, void* jsonResponse, Firebolt::Error status) + { + WPEFramework::Core::ProxyType<${method.result.json.type}>& proxyResponse = *(reinterpret_cast*>(jsonResponse)); + + ASSERT(proxyResponse.IsValid() == true); + + if (proxyResponse.IsValid() == true) { + + ${if.result.nonvoid}${method.result.initialization}${end.if.result.nonvoid} + ${method.result.json.type} jsonResult(proxyResponse->Value().c_str()); +${if.result.nonvoid}${method.result.instantiation}${end.if.result.nonvoid} + proxyResponse.Release(); + + I${info.Title}AsyncResponse& notifier = *(reinterpret_cast(notification)); + notifier.response(${method.result.name}, &status); + } + } + + /* ${method.name} - ${method.description} */ + void ${info.Title}Impl::request${method.Name}(${method.signature.params}${if.params}, ${end.if.params}I${info.Title}AsyncResponse& response, Firebolt::Error *err) + { + JsonObject jsonParameters; +${method.params.serialization} + + Firebolt::Error status = FireboltSDK::Async::Instance().Invoke<${method.result.json.type}>(_T("${info.title.lowercase}.${method.name}"), jsonParameters, ${method.name}AsyncResponseInnerCallback, reinterpret_cast(&response)); + if (status == Firebolt::Error::None) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "${info.Title}.${method.name} is successfully invoked"); + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "Error in getting Async::Invoke err = %d", status); + } + + if (err != nullptr) { + *err = status; + } + return; + } + void ${info.Title}Impl::abort${method.Name}(I${info.Title}AsyncResponse& response, Firebolt::Error *err) + { + Firebolt::Error status = FireboltSDK::Async::Instance().Abort(_T("${info.title.lowercase}.${method.name}"), reinterpret_cast(&response)); + if (err != nullptr) { + *err = status; + } + } diff --git a/languages/cpp/templates/modules/include/module.h b/languages/cpp/templates/modules/include/module.h index 04d52aef..ff3b1080 100644 --- a/languages/cpp/templates/modules/include/module.h +++ b/languages/cpp/templates/modules/include/module.h @@ -30,7 +30,7 @@ namespace ${info.Title} { ${if.types} // Types /* ${TYPES} */${end.if.types} -${if.providers}/* ${PROVIDERS} */${end.if.providers} +${if.providers}/* ${PROVIDERS} */${end.if.providers}${if.xuses}/* ${XUSES} */${end.if.xuses} ${if.methods}struct I${info.Title} { virtual ~I${info.Title}() = default; diff --git a/languages/cpp/templates/sections/xuses-interfaces.h b/languages/cpp/templates/sections/xuses-interfaces.h new file mode 100644 index 00000000..b5b580c7 --- /dev/null +++ b/languages/cpp/templates/sections/xuses-interfaces.h @@ -0,0 +1,7 @@ +struct I${info.Title}AsyncResponse { + + virtual ~I${info.Title}AsyncResponse() = default; + + virtual void response(const std::string& result, Firebolt::Error *err) = 0; +}; + diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 84276fb0..700594f2 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -29,7 +29,7 @@ import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates -import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod } from '../shared/modules.mjs' +import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isXUsesMethod, hasXUsesMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod } from '../shared/modules.mjs' import isEmpty from 'crocks/core/isEmpty.js' import { getPath as getJsonPath, getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, mergeAnyOf, mergeOneOf, getSafeEnumKeyName } from '../shared/json-schema.mjs' @@ -108,7 +108,7 @@ const getTemplate = (name, templates) => { } const getTemplateTypeForMethod = (method, type, templates) => { - const name = method.tags && method.tags.map(tag => tag.name.split(":").shift()).find(tag => Object.keys(templates).find(name => name.startsWith(`/${type}/${tag}.`))) || 'default' + const name = method.tags ? (isXUsesMethod(method) && Object.keys(templates).find(name => name.startsWith(`/${type}/x-uses.`))) ? 'x-uses' : (method.tags.map(tag => tag.name.split(":").shift()).find(tag => Object.keys(templates).find(name => name.startsWith(`/${type}/${tag}.`)))) || 'default' : 'default' const path = `/${type}/${name}` return getTemplate(path, templates) } @@ -595,6 +595,7 @@ const generateMacros = (obj, templates, languages, options = {}) => { } }) + const xusesInterfaces = generateXUsesInterfaces(obj, templates) const providerSubscribe = generateProviderSubscribe(obj, templates) const providerInterfaces = generateProviderInterfaces(obj, templates) const defaults = generateDefaults(obj, templates) @@ -611,6 +612,7 @@ const generateMacros = (obj, templates, languages, options = {}) => { eventsEnum, defaults, examples, + xusesInterfaces, providerInterfaces, providerSubscribe, version: getSemanticVersion(obj), @@ -674,6 +676,7 @@ const insertMacros = (fContents = '', macros = {}) => { fContents = fContents.replace(/\$\{if\.implementations\}(.*?)\$\{end\.if\.implementations\}/gms, (methods.trim() || macros.events.methods.trim() || macros.schemas.types.trim()) ? '$1' : '') fContents = fContents.replace(/\$\{if\.modules\}(.*?)\$\{end\.if\.modules\}/gms, (methods.trim() || macros.events.methods.trim()) ? '$1' : '') + fContents = fContents.replace(/\$\{if\.xuses\}(.*?)\$\{end\.if\.xuses\}/gms, macros.xusesInterfaces.trim() ? '$1' : '') fContents = fContents.replace(/\$\{if\.providers\}(.*?)\$\{end\.if\.providers\}/gms, macros.providerInterfaces.trim() ? '$1' : '') // Output the originally supported non-configurable methods & events macros @@ -705,6 +708,7 @@ const insertMacros = (fContents = '', macros = {}) => { }) fContents = fContents.replace(/[ \t]*\/\* \$\{PROVIDERS\} \*\/[ \t]*\n/, macros.providerInterfaces) + fContents = fContents.replace(/[ \t]*\/\* \$\{XUSES\} \*\/[ \t]*\n/, macros.xusesInterfaces) fContents = fContents.replace(/[ \t]*\/\* \$\{PROVIDERS_SUBSCRIBE\} \*\/[ \t]*\n/, macros.providerSubscribe) fContents = fContents.replace(/[ \t]*\/\* \$\{IMPORTS\} \*\/[ \t]*\n/, macros.imports) fContents = fContents.replace(/[ \t]*\/\* \$\{INITIALIZATION\} \*\/[ \t]*\n/, macros.initialization) @@ -1717,6 +1721,18 @@ function insertCapabilityMacros(template, capabilities, method, module) { return content.join() } +function generateXUsesInterfaces(json, templates) { + let template = '' + if (hasXUsesMethods(json)) { + const suffix = state.destination ? state.destination.split('.').pop() : '' + template = getTemplate(suffix ? `/sections/xuses-interfaces.${suffix}` : '/sections/xuses-interfaces', templates) + if (!template) { + template = getTemplate('/sections/xuses-interfaces', templates) + } + } + return template +} + function generateProviderSubscribe(json, templates) { const interfaces = getProvidedCapabilities(json) const suffix = state.destination ? state.destination.split('.').pop() : '' diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index 6ef65f23..8413cf75 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -289,6 +289,16 @@ const isPolymorphicReducer = compose( getPath(['tags']) ) +const isXUsesMethod = compose( + option(false), + map(_ => true), + chain(find(and( + hasProp('x-uses'), + propSatisfies('x-allow-focus', focus => (focus === true)) + ))), + getPath(['tags']) +) + const hasTitle = compose( option(false), map(isString), @@ -359,6 +369,8 @@ const getPublicEvents = compose( const hasPublicInterfaces = json => json.methods && json.methods.filter(m => m.tags && m.tags.find(t=>t['x-provides'])).length > 0 const hasPublicAPIs = json => hasPublicInterfaces(json) || (json.methods && json.methods.filter( method => !method.tags.find(tag => tag.name === 'rpc-only')).length > 0) +const hasXUsesMethods = json => json.methods && json.methods.filter(m => isXUsesMethod(m)).length > 0 + const eventDefaults = event => { event.tags = [ @@ -1423,6 +1435,8 @@ export { isPublicEventMethod, hasPublicAPIs, hasPublicInterfaces, + isXUsesMethod, + hasXUsesMethods, isPolymorphicReducer, isPolymorphicPullMethod, isTemporalSetMethod,