diff --git a/doc/schemas/app_openapi.json b/doc/schemas/app_openapi.json index f87cd88370f6..125a1350bc49 100644 --- a/doc/schemas/app_openapi.json +++ b/doc/schemas/app_openapi.json @@ -263,12 +263,13 @@ "info": { "description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.", "title": "CCF Sample Logging App", - "version": "2.3.1" + "version": "2.4.1" }, "openapi": "3.0.0", "paths": { "/app/api": { "get": { + "operationId": "GetAppApi", "responses": { "200": { "content": { @@ -294,6 +295,7 @@ "get": { "deprecated": true, "description": "Permitted SGX code identities", + "operationId": "GetAppCode", "responses": { "200": { "content": { @@ -318,6 +320,7 @@ "/app/commit": { "get": { "description": "Latest transaction ID that has been committed on the service", + "operationId": "GetAppCommit", "parameters": [ { "in": "query", @@ -359,6 +362,7 @@ }, "/app/custom_auth": { "get": { + "operationId": "GetAppCustomAuth", "responses": { "200": { "content": { @@ -381,6 +385,7 @@ }, "/app/log/cose_signed_content": { "post": { + "operationId": "PostAppLogCoseSignedContent", "responses": { "200": { "content": { @@ -408,6 +413,7 @@ }, "/app/log/private": { "delete": { + "operationId": "DeleteAppLogPrivate", "parameters": [ { "in": "query", @@ -446,6 +452,7 @@ } }, "get": { + "operationId": "GetAppLogPrivate", "parameters": [ { "in": "query", @@ -484,6 +491,7 @@ } }, "post": { + "operationId": "PostAppLogPrivate", "requestBody": { "content": { "application/json": { @@ -524,6 +532,7 @@ }, "/app/log/private/admin_only": { "post": { + "operationId": "PostAppLogPrivateAdminOnly", "requestBody": { "content": { "application/json": { @@ -556,6 +565,7 @@ }, "/app/log/private/all": { "delete": { + "operationId": "DeleteAppLogPrivateAll", "responses": { "200": { "content": { @@ -586,6 +596,7 @@ }, "/app/log/private/anonymous": { "post": { + "operationId": "PostAppLogPrivateAnonymous", "requestBody": { "content": { "application/json": { @@ -618,6 +629,7 @@ }, "/app/log/private/anonymous/v2": { "post": { + "operationId": "PostAppLogPrivateAnonymousV2", "requestBody": { "content": { "application/json": { @@ -650,6 +662,7 @@ }, "/app/log/private/backup": { "get": { + "operationId": "GetAppLogPrivateBackup", "parameters": [ { "in": "query", @@ -690,6 +703,7 @@ }, "/app/log/private/committed": { "get": { + "operationId": "GetAppLogPrivateCommitted", "parameters": [ { "in": "query", @@ -722,6 +736,7 @@ }, "/app/log/private/count": { "get": { + "operationId": "GetAppLogPrivateCount", "responses": { "200": { "content": { @@ -752,6 +767,7 @@ }, "/app/log/private/historical": { "get": { + "operationId": "GetAppLogPrivateHistorical", "parameters": [ { "in": "query", @@ -792,6 +808,7 @@ }, "/app/log/private/historical/sparse": { "get": { + "operationId": "GetAppLogPrivateHistoricalSparse", "parameters": [ { "in": "query", @@ -840,6 +857,7 @@ }, "/app/log/private/historical_receipt": { "get": { + "operationId": "GetAppLogPrivateHistoricalReceipt", "parameters": [ { "in": "query", @@ -880,6 +898,7 @@ }, "/app/log/private/install_committed_index": { "post": { + "operationId": "PostAppLogPrivateInstallCommittedIndex", "responses": { "204": { "description": "Default response description" @@ -895,6 +914,7 @@ }, "/app/log/private/prefix_cert": { "post": { + "operationId": "PostAppLogPrivatePrefixCert", "requestBody": { "content": { "application/json": { @@ -937,6 +957,7 @@ } ], "post": { + "operationId": "PostAppLogPrivateRawTextId", "responses": { "200": { "description": "Default response description" @@ -960,6 +981,7 @@ }, "/app/log/private/uninstall_committed_index": { "post": { + "operationId": "PostAppLogPrivateUninstallCommittedIndex", "responses": { "204": { "description": "Default response description" @@ -975,6 +997,7 @@ }, "/app/log/public": { "delete": { + "operationId": "DeleteAppLogPublic", "parameters": [ { "in": "query", @@ -1013,6 +1036,7 @@ } }, "get": { + "operationId": "GetAppLogPublic", "parameters": [ { "in": "query", @@ -1051,6 +1075,7 @@ } }, "post": { + "operationId": "PostAppLogPublic", "requestBody": { "content": { "application/json": { @@ -1091,6 +1116,7 @@ }, "/app/log/public/all": { "delete": { + "operationId": "DeleteAppLogPublicAll", "responses": { "200": { "content": { @@ -1121,6 +1147,7 @@ }, "/app/log/public/backup": { "get": { + "operationId": "GetAppLogPublicBackup", "parameters": [ { "in": "query", @@ -1161,6 +1188,7 @@ }, "/app/log/public/count": { "get": { + "operationId": "GetAppLogPublicCount", "responses": { "200": { "content": { @@ -1191,6 +1219,7 @@ }, "/app/log/public/historical/range": { "get": { + "operationId": "GetAppLogPublicHistoricalRange", "parameters": [ { "in": "query", @@ -1247,6 +1276,7 @@ }, "/app/log/public/historical_receipt": { "get": { + "operationId": "GetAppLogPublicHistoricalReceipt", "parameters": [ { "in": "query", @@ -1287,6 +1317,7 @@ }, "/app/log/request_query": { "get": { + "operationId": "GetAppLogRequestQuery", "responses": { "200": { "content": { @@ -1309,6 +1340,7 @@ }, "/app/multi_auth": { "post": { + "operationId": "PostAppMultiAuth", "responses": { "200": { "content": { @@ -1341,6 +1373,7 @@ "/app/receipt": { "get": { "description": "A signed statement from the service over a transaction entry in the ledger", + "operationId": "GetAppReceipt", "parameters": [ { "in": "query", @@ -1375,6 +1408,7 @@ "/app/tx": { "get": { "description": "Possible statuses returned are Unknown, Pending, Committed or Invalid.", + "operationId": "GetAppTx", "parameters": [ { "in": "query", diff --git a/doc/schemas/gov_openapi.json b/doc/schemas/gov_openapi.json index 855144deeb4a..5f17a2d971c8 100644 --- a/doc/schemas/gov_openapi.json +++ b/doc/schemas/gov_openapi.json @@ -1351,12 +1351,13 @@ "info": { "description": "This API is used to submit and query proposals which affect CCF's public governance tables.", "title": "CCF Governance API", - "version": "4.1.8" + "version": "4.2.8" }, "openapi": "3.0.0", "paths": { "/gov/ack": { "post": { + "operationId": "PostGovAck", "requestBody": { "content": { "application/json": { @@ -1388,6 +1389,7 @@ }, "/gov/ack/update_state_digest": { "post": { + "operationId": "PostGovAckUpdateStateDigest", "responses": { "200": { "content": { @@ -1416,6 +1418,7 @@ }, "/gov/api": { "get": { + "operationId": "GetGovApi", "responses": { "200": { "content": { @@ -1441,6 +1444,7 @@ "get": { "deprecated": true, "description": "Permitted SGX code identities", + "operationId": "GetGovCode", "responses": { "200": { "content": { @@ -1465,6 +1469,7 @@ "/gov/commit": { "get": { "description": "Latest transaction ID that has been committed on the service", + "operationId": "GetGovCommit", "parameters": [ { "in": "query", @@ -1506,6 +1511,7 @@ }, "/gov/encrypted_recovery_share/{member_id}": { "get": { + "operationId": "GetGovEncryptedRecoveryShareMemberId", "responses": { "200": { "content": { @@ -1540,6 +1546,7 @@ "/gov/jwt_keys/all": { "get": { "deprecated": true, + "operationId": "GetGovJwtKeysAll", "responses": { "200": { "content": { @@ -1563,6 +1570,7 @@ }, "/gov/kv/constitution": { "get": { + "operationId": "GetGovKvConstitution", "responses": { "200": { "content": { @@ -1585,6 +1593,7 @@ }, "/gov/kv/cose_history": { "get": { + "operationId": "GetGovKvCoseHistory", "responses": { "200": { "content": { @@ -1607,6 +1616,7 @@ }, "/gov/kv/cose_recent_proposals": { "get": { + "operationId": "GetGovKvCoseRecentProposals", "responses": { "200": { "content": { @@ -1629,6 +1639,7 @@ }, "/gov/kv/endpoints": { "get": { + "operationId": "GetGovKvEndpoints", "responses": { "200": { "content": { @@ -1651,6 +1662,7 @@ }, "/gov/kv/history": { "get": { + "operationId": "GetGovKvHistory", "responses": { "200": { "content": { @@ -1673,6 +1685,7 @@ }, "/gov/kv/interpreter/flush": { "get": { + "operationId": "GetGovKvInterpreterFlush", "responses": { "200": { "content": { @@ -1695,6 +1708,7 @@ }, "/gov/kv/js_runtime_options": { "get": { + "operationId": "GetGovKvJsRuntimeOptions", "responses": { "200": { "content": { @@ -1717,6 +1731,7 @@ }, "/gov/kv/jwt/issuers": { "get": { + "operationId": "GetGovKvJwtIssuers", "responses": { "200": { "content": { @@ -1739,6 +1754,7 @@ }, "/gov/kv/jwt/public_signing_key": { "get": { + "operationId": "GetGovKvJwtPublicSigningKey", "responses": { "200": { "content": { @@ -1761,6 +1777,7 @@ }, "/gov/kv/jwt/public_signing_key_issuer": { "get": { + "operationId": "GetGovKvJwtPublicSigningKeyIssuer", "responses": { "200": { "content": { @@ -1783,6 +1800,7 @@ }, "/gov/kv/jwt/public_signing_keys_metadata": { "get": { + "operationId": "GetGovKvJwtPublicSigningKeysMetadata", "responses": { "200": { "content": { @@ -1805,6 +1823,7 @@ }, "/gov/kv/members/acks": { "get": { + "operationId": "GetGovKvMembersAcks", "responses": { "200": { "content": { @@ -1827,6 +1846,7 @@ }, "/gov/kv/members/certs": { "get": { + "operationId": "GetGovKvMembersCerts", "responses": { "200": { "content": { @@ -1849,6 +1869,7 @@ }, "/gov/kv/members/encryption_public_keys": { "get": { + "operationId": "GetGovKvMembersEncryptionPublicKeys", "responses": { "200": { "content": { @@ -1871,6 +1892,7 @@ }, "/gov/kv/members/info": { "get": { + "operationId": "GetGovKvMembersInfo", "responses": { "200": { "content": { @@ -1893,6 +1915,7 @@ }, "/gov/kv/modules": { "get": { + "operationId": "GetGovKvModules", "responses": { "200": { "content": { @@ -1915,6 +1938,7 @@ }, "/gov/kv/modules_quickjs_bytecode": { "get": { + "operationId": "GetGovKvModulesQuickjsBytecode", "responses": { "200": { "content": { @@ -1937,6 +1961,7 @@ }, "/gov/kv/modules_quickjs_version": { "get": { + "operationId": "GetGovKvModulesQuickjsVersion", "responses": { "200": { "content": { @@ -1959,6 +1984,7 @@ }, "/gov/kv/nodes/code_ids": { "get": { + "operationId": "GetGovKvNodesCodeIds", "responses": { "200": { "content": { @@ -1981,6 +2007,7 @@ }, "/gov/kv/nodes/endorsed_certificates": { "get": { + "operationId": "GetGovKvNodesEndorsedCertificates", "responses": { "200": { "content": { @@ -2003,6 +2030,7 @@ }, "/gov/kv/nodes/info": { "get": { + "operationId": "GetGovKvNodesInfo", "responses": { "200": { "content": { @@ -2025,6 +2053,7 @@ }, "/gov/kv/nodes/snp/host_data": { "get": { + "operationId": "GetGovKvNodesSnpHostData", "responses": { "200": { "content": { @@ -2047,6 +2076,7 @@ }, "/gov/kv/nodes/snp/measurements": { "get": { + "operationId": "GetGovKvNodesSnpMeasurements", "responses": { "200": { "content": { @@ -2069,6 +2099,7 @@ }, "/gov/kv/nodes/snp/uvm_endorsements": { "get": { + "operationId": "GetGovKvNodesSnpUvmEndorsements", "responses": { "200": { "content": { @@ -2091,6 +2122,7 @@ }, "/gov/kv/proposals": { "get": { + "operationId": "GetGovKvProposals", "responses": { "200": { "content": { @@ -2113,6 +2145,7 @@ }, "/gov/kv/proposals_info": { "get": { + "operationId": "GetGovKvProposalsInfo", "responses": { "200": { "content": { @@ -2135,6 +2168,7 @@ }, "/gov/kv/service/acme_certificates": { "get": { + "operationId": "GetGovKvServiceAcmeCertificates", "responses": { "200": { "content": { @@ -2157,6 +2191,7 @@ }, "/gov/kv/service/config": { "get": { + "operationId": "GetGovKvServiceConfig", "responses": { "200": { "content": { @@ -2179,6 +2214,7 @@ }, "/gov/kv/service/info": { "get": { + "operationId": "GetGovKvServiceInfo", "responses": { "200": { "content": { @@ -2201,6 +2237,7 @@ }, "/gov/kv/service/previous_service_identity": { "get": { + "operationId": "GetGovKvServicePreviousServiceIdentity", "responses": { "200": { "content": { @@ -2223,6 +2260,7 @@ }, "/gov/kv/tls/ca_cert_bundles": { "get": { + "operationId": "GetGovKvTlsCaCertBundles", "responses": { "200": { "content": { @@ -2245,6 +2283,7 @@ }, "/gov/kv/users/certs": { "get": { + "operationId": "GetGovKvUsersCerts", "responses": { "200": { "content": { @@ -2267,6 +2306,7 @@ }, "/gov/kv/users/info": { "get": { + "operationId": "GetGovKvUsersInfo", "responses": { "200": { "content": { @@ -2290,6 +2330,7 @@ "/gov/members": { "get": { "deprecated": true, + "operationId": "GetGovMembers", "responses": { "200": { "content": { @@ -2313,6 +2354,7 @@ }, "/gov/proposals": { "get": { + "operationId": "GetGovProposals", "responses": { "200": { "content": { @@ -2334,6 +2376,7 @@ } }, "post": { + "operationId": "PostGovProposals", "requestBody": { "content": { "application/json": { @@ -2372,6 +2415,7 @@ }, "/gov/proposals/{proposal_id}": { "get": { + "operationId": "GetGovProposalsProposalId", "responses": { "200": { "content": { @@ -2405,6 +2449,7 @@ }, "/gov/proposals/{proposal_id}/actions": { "get": { + "operationId": "GetGovProposalsProposalIdActions", "responses": { "200": { "content": { @@ -2448,6 +2493,7 @@ } ], "post": { + "operationId": "PostGovProposalsProposalIdBallots", "requestBody": { "content": { "application/json": { @@ -2486,6 +2532,7 @@ }, "/gov/proposals/{proposal_id}/ballots/{member_id}": { "get": { + "operationId": "GetGovProposalsProposalIdBallotsMemberId", "responses": { "200": { "content": { @@ -2537,6 +2584,7 @@ } ], "post": { + "operationId": "PostGovProposalsProposalIdWithdraw", "responses": { "200": { "content": { @@ -2566,6 +2614,7 @@ "/gov/receipt": { "get": { "description": "A signed statement from the service over a transaction entry in the ledger", + "operationId": "GetGovReceipt", "parameters": [ { "in": "query", @@ -2600,6 +2649,7 @@ "/gov/recovery_share": { "get": { "deprecated": true, + "operationId": "GetGovRecoveryShare", "responses": { "200": { "content": { @@ -2626,6 +2676,7 @@ } }, "post": { + "operationId": "PostGovRecoveryShare", "requestBody": { "content": { "application/json": { @@ -2665,6 +2716,7 @@ "/gov/tx": { "get": { "description": "Possible statuses returned are Unknown, Pending, Committed or Invalid.", + "operationId": "GetGovTx", "parameters": [ { "in": "query", diff --git a/doc/schemas/node_openapi.json b/doc/schemas/node_openapi.json index 53602354c9d4..a74b5b253b8e 100644 --- a/doc/schemas/node_openapi.json +++ b/doc/schemas/node_openapi.json @@ -896,12 +896,13 @@ "info": { "description": "This API provides public, uncredentialed access to service and node state.", "title": "CCF Public Node API", - "version": "4.9.2" + "version": "4.10.2" }, "openapi": "3.0.0", "paths": { "/node/api": { "get": { + "operationId": "GetNodeApi", "responses": { "200": { "content": { @@ -925,6 +926,7 @@ }, "/node/backup": { "get": { + "operationId": "GetNodeBackup", "responses": { "200": { "description": "Default response description" @@ -942,6 +944,7 @@ "get": { "deprecated": true, "description": "Permitted SGX code identities", + "operationId": "GetNodeCode", "responses": { "200": { "content": { @@ -966,6 +969,7 @@ "/node/commit": { "get": { "description": "Latest transaction ID that has been committed on the service", + "operationId": "GetNodeCommit", "parameters": [ { "in": "query", @@ -1007,6 +1011,7 @@ }, "/node/config": { "get": { + "operationId": "GetNodeConfig", "responses": { "200": { "content": { @@ -1029,6 +1034,7 @@ }, "/node/consensus": { "get": { + "operationId": "GetNodeConsensus", "responses": { "200": { "content": { @@ -1051,6 +1057,7 @@ }, "/node/index/strategies": { "get": { + "operationId": "GetNodeIndexStrategies", "responses": { "200": { "content": { @@ -1073,6 +1080,7 @@ }, "/node/js_metrics": { "get": { + "operationId": "GetNodeJsMetrics", "responses": { "200": { "content": { @@ -1095,6 +1103,7 @@ }, "/node/jwt_keys/refresh/metrics": { "get": { + "operationId": "GetNodeJwtKeysRefreshMetrics", "responses": { "200": { "content": { @@ -1117,6 +1126,7 @@ }, "/node/memory": { "get": { + "operationId": "GetNodeMemory", "responses": { "200": { "content": { @@ -1139,6 +1149,7 @@ }, "/node/metrics": { "get": { + "operationId": "GetNodeMetrics", "responses": { "200": { "content": { @@ -1161,6 +1172,7 @@ }, "/node/network": { "get": { + "operationId": "GetNodeNetwork", "responses": { "200": { "content": { @@ -1183,6 +1195,7 @@ }, "/node/network/nodes": { "get": { + "operationId": "GetNodeNetworkNodes", "parameters": [ { "in": "query", @@ -1231,6 +1244,7 @@ }, "/node/network/nodes/primary": { "get": { + "operationId": "GetNodeNetworkNodesPrimary", "responses": { "200": { "content": { @@ -1253,6 +1267,7 @@ }, "/node/network/nodes/self": { "get": { + "operationId": "GetNodeNetworkNodesSelf", "responses": { "200": { "content": { @@ -1275,6 +1290,7 @@ }, "/node/network/nodes/{node_id}": { "delete": { + "operationId": "DeleteNodeNetworkNodesNodeId", "responses": { "200": { "content": { @@ -1295,6 +1311,7 @@ } }, "get": { + "operationId": "GetNodeNetworkNodesNodeId", "responses": { "200": { "content": { @@ -1327,6 +1344,7 @@ }, "/node/network/removable_nodes": { "get": { + "operationId": "GetNodeNetworkRemovableNodes", "responses": { "200": { "content": { @@ -1349,6 +1367,7 @@ }, "/node/primary": { "get": { + "operationId": "GetNodePrimary", "responses": { "200": { "description": "Default response description" @@ -1362,6 +1381,7 @@ } }, "head": { + "operationId": "HeadNodePrimary", "responses": { "200": { "description": "Default response description" @@ -1377,6 +1397,7 @@ }, "/node/quotes": { "get": { + "operationId": "GetNodeQuotes", "responses": { "200": { "content": { @@ -1399,6 +1420,7 @@ }, "/node/quotes/self": { "get": { + "operationId": "GetNodeQuotesSelf", "responses": { "200": { "content": { @@ -1421,6 +1443,7 @@ }, "/node/ready/app": { "get": { + "operationId": "GetNodeReadyApp", "responses": { "204": { "description": "Default response description" @@ -1436,6 +1459,7 @@ }, "/node/ready/gov": { "get": { + "operationId": "GetNodeReadyGov", "responses": { "204": { "description": "Default response description" @@ -1452,6 +1476,7 @@ "/node/receipt": { "get": { "description": "A signed statement from the service over a transaction entry in the ledger", + "operationId": "GetNodeReceipt", "parameters": [ { "in": "query", @@ -1485,6 +1510,7 @@ }, "/node/self_signed_certificate": { "get": { + "operationId": "GetNodeSelfSignedCertificate", "responses": { "200": { "content": { @@ -1507,6 +1533,7 @@ }, "/node/service/configuration": { "get": { + "operationId": "GetNodeServiceConfiguration", "responses": { "200": { "content": { @@ -1529,6 +1556,7 @@ }, "/node/service/previous_identity": { "get": { + "operationId": "GetNodeServicePreviousIdentity", "responses": { "200": { "content": { @@ -1551,6 +1579,7 @@ }, "/node/state": { "get": { + "operationId": "GetNodeState", "responses": { "200": { "content": { @@ -1574,6 +1603,7 @@ "/node/tx": { "get": { "description": "Possible statuses returned are Unknown, Pending, Committed or Invalid.", + "operationId": "GetNodeTx", "parameters": [ { "in": "query", @@ -1607,6 +1637,7 @@ }, "/node/version": { "get": { + "operationId": "GetNodeVersion", "responses": { "200": { "content": { diff --git a/include/ccf/ds/nonstd.h b/include/ccf/ds/nonstd.h index 97ba29c13f25..c7dc4780acf7 100644 --- a/include/ccf/ds/nonstd.h +++ b/include/ccf/ds/nonstd.h @@ -6,11 +6,15 @@ #include #include #include +#include #include #include #include #include +#define FMT_HEADER_ONLY +#include + /** * This file defines various type traits and utils that are not available in the * standard library. Some are added in future versions of the standard library, @@ -288,4 +292,32 @@ namespace ccf::nonstd return resolved.lexically_normal().string(); } + + static inline std::string camel_case( + std::string s, + // Should the first character be upper-cased? + bool camel_first = true, + // Regex fragment to identify which characters should be upper-cased, by + // matching a separator preceding them. Default is to match any + // non-alphanumeric character + const std::string& separator_regex = "[^[:alnum:]]") + { + // Replacement is always a 1-character string + std::string replacement(1, '\0'); + + std::string prefix_matcher = + camel_first ? fmt::format("(^|{})", separator_regex) : separator_regex; + std::regex re(prefix_matcher + "[a-z]"); + std::smatch match; + + while (std::regex_search(s, match, re)) + { + // Replacement is the upper-casing of the final character from the match + replacement[0] = std::toupper(match.str()[match.length() - 1]); + + s = s.replace(match.position(), match.length(), replacement); + } + + return s; + } } \ No newline at end of file diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index a690452b7c37..6465aa35116a 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -454,7 +454,7 @@ namespace loggingapp "recording messages at client-specified IDs. It demonstrates most of " "the features available to CCF apps."; - openapi_info.document_version = "2.3.1"; + openapi_info.document_version = "2.4.1"; index_per_public_key = std::make_shared( PUBLIC_RECORDS, context, 10000, 20); diff --git a/src/ds/test/nonstd.cpp b/src/ds/test/nonstd.cpp index ccd352b73719..7d2122bf0bbe 100644 --- a/src/ds/test/nonstd.cpp +++ b/src/ds/test/nonstd.cpp @@ -319,4 +319,72 @@ TEST_CASE("envvars" * doctest::test_suite("nonstd")) fmt::format("/{}/{}", test_value1, test_value2) == ccf::nonstd::expand_envvars_in_path("/$TEST_ENV_VAR1/$TEST_ENV_VAR2")); } +} + +TEST_CASE("camel_case" * doctest::test_suite("nonstd")) +{ + { + INFO("Default separator"); + REQUIRE(ccf::nonstd::camel_case("") == ""); + REQUIRE(ccf::nonstd::camel_case("abc") == "Abc"); + REQUIRE(ccf::nonstd::camel_case("abc", false) == "abc"); + + REQUIRE(ccf::nonstd::camel_case("hello world") == "HelloWorld"); + REQUIRE(ccf::nonstd::camel_case("hello world", false) == "helloWorld"); + + REQUIRE( + ccf::nonstd::camel_case("standard_snake_case_value") == + "StandardSnakeCaseValue"); + REQUIRE( + ccf::nonstd::camel_case("standard_snake_case_value", false) == + "standardSnakeCaseValue"); + + REQUIRE( + ccf::nonstd::camel_case( + "camel-with.many/many!many_many,many|many$separators") == + "CamelWithManyManyManyManyManyManySeparators"); + REQUIRE( + ccf::nonstd::camel_case( + "camel-with.many/many!many_many,many|many$separators", false) == + "camelWithManyManyManyManyManyManySeparators"); + + REQUIRE( + ccf::nonstd::camel_case("1handling2of3.numbers") == + "1handling2of3Numbers"); + REQUIRE( + ccf::nonstd::camel_case("1handling2of3.numbers", false) == + "1handling2of3Numbers"); + + REQUIRE( + ccf::nonstd::camel_case( + "camel_With-Existing_mixed-casing_Is-1Perhaps_2Surprising") == + "Camel_With-ExistingMixedCasing_Is-1Perhaps_2Surprising"); + REQUIRE( + ccf::nonstd::camel_case( + "camel_With-Existing_mixed-casing_Is-1Perhaps_2Surprising", false) == + "camel_With-ExistingMixedCasing_Is-1Perhaps_2Surprising"); + } + { + INFO("Custom separators"); + REQUIRE(ccf::nonstd::camel_case("hello world", true, "_") == "Hello world"); + REQUIRE( + ccf::nonstd::camel_case("hello world", false, "_") == "hello world"); + + REQUIRE(ccf::nonstd::camel_case("hello_world", true, "_") == "HelloWorld"); + REQUIRE(ccf::nonstd::camel_case("hello_world", false, "_") == "helloWorld"); + + REQUIRE( + ccf::nonstd::camel_case("what-about-/mixed/separators", true, "-") == + "WhatAbout-/mixed/separators"); + REQUIRE( + ccf::nonstd::camel_case("what-about-/mixed/separators", false, "-") == + "whatAbout-/mixed/separators"); + + REQUIRE( + ccf::nonstd::camel_case("what-about-/mixed/separators", true, "/") == + "What-about-MixedSeparators"); + REQUIRE( + ccf::nonstd::camel_case("what-about-/mixed/separators", false, "/") == + "what-about-MixedSeparators"); + } } \ No newline at end of file diff --git a/src/endpoints/endpoint_registry.cpp b/src/endpoints/endpoint_registry.cpp index f5dd50b5f132..e84a2bead4eb 100644 --- a/src/endpoints/endpoint_registry.cpp +++ b/src/endpoints/endpoint_registry.cpp @@ -34,6 +34,26 @@ namespace ccf::endpoints ds::openapi::path(document, endpoint->full_uri_path), http_verb.value()); + // Add what appears a *mandatory* operationId, which is expected to be + // unique across the spec. + // A1) Eliminate '{' and '}' from the path but keep the string inbetween. + std::string p = + std::regex_replace(endpoint->full_uri_path, std::regex("[{}]"), ""); + // A2) Camel-Case what remains at the path separator. + p = ccf::nonstd::camel_case(p); + + // B1) Get the HTTP verb as a string (it's all caps). + std::string s = llhttp_method_name(http_verb.value()); + // B2) Lowercase it. + ccf::nonstd::to_lower(s); + // B3) Camel-Case it, i.e., the first char. + s = ccf::nonstd::camel_case(s, true); + + // C) Concatenate the camel-cased verb and path. For example, this gives + // us "PostAppLogPrivateRawTextId" for the verb POST and the path + // "/app/log/private/raw_text/{id}". + path_op["operationId"] = fmt::format("{}{}", s, p); + // Path Operation must contain at least one response - if none has been // defined, assume this can return 200 if (ds::openapi::responses(path_op).empty()) diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index 6d6262b8ae0b..5a0b1a778563 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -595,7 +595,7 @@ namespace ccf openapi_info.description = "This API is used to submit and query proposals which affect CCF's " "public governance tables."; - openapi_info.document_version = "4.1.8"; + openapi_info.document_version = "4.2.8"; } static std::optional get_caller_member_id( diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 03b9e4f7037f..9544fc713901 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -404,7 +404,7 @@ namespace ccf openapi_info.description = "This API provides public, uncredentialed access to service and node " "state."; - openapi_info.document_version = "4.9.2"; + openapi_info.document_version = "4.10.2"; } void init_handlers() override