From b85b3062a5049ad6fb18f648a7abede8a0e27432 Mon Sep 17 00:00:00 2001 From: Kody Stribrny <89810515+kstribrnAmzn@users.noreply.github.com> Date: Mon, 8 Jul 2024 07:24:13 -0700 Subject: [PATCH] Optional status details (#105) * Feat: Support StatusDetails messaging in Update Message Allows users to send a valid JSON key-value set to the jobs service. This API does lightweight validation of the status details payload. --- docs/doxygen/include/size_table.md | 8 +- source/include/jobs.h | 61 +++++++++---- source/jobs.c | 107 +++++++++++++++------- test/unit-test/jobs_utest.c | 138 ++++++++++++++++++++++++----- 4 files changed, 243 insertions(+), 71 deletions(-) diff --git a/docs/doxygen/include/size_table.md b/docs/doxygen/include/size_table.md index 67eb14c..6296ec7 100644 --- a/docs/doxygen/include/size_table.md +++ b/docs/doxygen/include/size_table.md @@ -9,8 +9,8 @@ jobs.c -
2.8K
-
2.5K
+
2.9K
+
2.6K
job_parser.c @@ -29,7 +29,7 @@ Total estimates -
6.8K
-
6.0K
+
6.9K
+
6.1K
diff --git a/source/include/jobs.h b/source/include/jobs.h index adeb223..0939e54 100644 --- a/source/include/jobs.h +++ b/source/include/jobs.h @@ -150,6 +150,9 @@ #define JOBS_API_EXPECTED_VERSION "\",\"expectedVersion\":\"" #define JOBS_API_EXPECTED_VERSION_LENGTH ( sizeof( JOBS_API_EXPECTED_VERSION ) - 1U ) +#define JOBS_API_STATUS_DETAILS "\",\"statusDetails\":\"" +#define JOBS_API_STATUS_DETAILS_LENGTH ( sizeof( JOBS_API_STATUS_DETAILS ) - 1U ) + #define JOBS_API_COMMON_LENGTH( thingNameLength ) \ ( JOBS_API_PREFIX_LENGTH + ( thingNameLength ) + JOBS_API_BRIDGE_LENGTH ) @@ -314,6 +317,32 @@ typedef enum JobsMaxTopic } JobsTopic_t; +/** + * @ingroup jobs_struct_types + * @brief Structure for Jobs_UpdateMsg request parameters. + * + * @note For optional fields setting a pointer to NULL or + * the length to 0U will disable this field from being used. + * + * + * @note Optional fields include: + * * expectedVersion + * * expectedVersionLength + * * statusDetails + * * statusDetailsLength + * + * @note The status details must be a JSON formatted key-value + * pair. + */ +typedef struct +{ + JobCurrentStatus_t status; /**< Status to update the job to. */ + const char * expectedVersion; /**< Expected version, optional. */ + size_t expectedVersionLength; /**< Expected version length, optional. */ + const char * statusDetails; /**< JSON key-value pair, optional. */ + size_t statusDetailsLength; /**< JSON key-value pair length, optional. */ +} JobsUpdateRequest_t; + /*-----------------------------------------------------------*/ /** @@ -838,31 +867,33 @@ JobsStatus_t Jobs_Update( char * buffer, /** * @brief Populate a message string for an UpdateJobExecution request. * - * @param status Current status of the job - * @param expectedVersion The version that is expected, NULL if no version is expected - * @param expectedVersionLength The length of the expectedVersion string, 0U if no version is expected - * @param buffer The buffer to be written to - * @param bufferSize the size of the buffer + * @param request A jobs update request structure. + * @param buffer The buffer to be written to. + * @param bufferSize the size of the buffer. * - * @return 0 if write to buffer fails - * @return messageLength if the write is successful + * @return 0 if write to buffer fails. + * @return messageLength if the write is successful. * * Example * @code{c} * * // The Following Example shows usage of the Jobs_UpdateMsg API to * // generate a message string for the UpdateJobExecution API - * // of the AWS IoT Jobs Service + * // of the AWS IoT Jobs Service. * * const char * expectedVersion = "2"; - * size_t expectedVersionLength = ( sizeof(expectedVersion ) - 1U ); - * JobCurrentStatus_t status = Succeeded; + * const chat * statusDetails = "{\"key\":\"value\"}"; * char messageBuffer[ UPDATE_JOB_MSG_LENGTH ] = {0}; * size_t messageLength = 0U; * - * messageLength = Jobs_UpdateMsg( status, - * expectedVersion, - * expectedVersionLength, + * JobsUpdateRequest_t request; + * request.status = Succeeded; + * request.expectedVersion = expectedVersion; + * request.expectedVersionLength = ( sizeof( expectedVersion ) - 1U ); + * request.statusDetails = statusDetails; + * request.statusDetailsLength = ( sizeof( statusDetails ) - 1U ); + * + * messageLength = Jobs_UpdateMsg( request * messageBuffer, * UPDATE_JOB_MSG_LENGTH ); * @@ -876,9 +907,7 @@ JobsStatus_t Jobs_Update( char * buffer, * @endcode */ /* @[declare_jobs_updatemsg] */ -size_t Jobs_UpdateMsg( JobCurrentStatus_t status, - const char * expectedVersion, - size_t expectedVersionLength, +size_t Jobs_UpdateMsg( JobsUpdateRequest_t request, char * buffer, size_t bufferSize ); /* @[declare_jobs_updatemsg] */ diff --git a/source/jobs.c b/source/jobs.c index e66755d..bd15740 100644 --- a/source/jobs.c +++ b/source/jobs.c @@ -82,6 +82,15 @@ static const size_t apiTopicLength[] = JOBS_API_UPDATE_LENGTH + JOBS_API_FAILURE_LENGTH, }; +static const char * const jobStatusString[] = +{ + "QUEUED", + "IN_PROGRESS", + "FAILED", + "SUCCEEDED", + "REJECTED" +}; + /** * @brief Predicate returns true for a valid thing name or job ID character. * @@ -813,53 +822,91 @@ JobsStatus_t Jobs_Update( char * buffer, return ret; } -size_t Jobs_UpdateMsg( JobCurrentStatus_t status, - const char * expectedVersion, - size_t expectedVersionLength, - char * buffer, - size_t bufferSize ) +/** + * @brief Get the total length of optional fields provided for + * the Jobs_UpdateMsg. These optional fields, if provided, require + * additional buffer space. + * + * @param request A JobsUpdateRequest_t containing the optional fields. + * @return size_t The buffer space required for the optional fields. + */ +static size_t getOptionalFieldsLength( JobsUpdateRequest_t request ) { - static const char * const jobStatusString[] = + size_t minimumOptionalFieldsBufferSize = 0U; + + if( ( request.expectedVersion != NULL ) && ( request.expectedVersionLength > 0U ) ) { - "QUEUED", - "IN_PROGRESS", - "FAILED", - "SUCCEEDED", - "REJECTED" - }; + minimumOptionalFieldsBufferSize += JOBS_API_EXPECTED_VERSION_LENGTH + request.expectedVersionLength; + } - static const size_t jobStatusStringLengths[] = + if( ( request.statusDetails != NULL ) && ( request.statusDetailsLength ) ) { - CONST_STRLEN( "QUEUED" ), - CONST_STRLEN( "IN_PROGRESS" ), - CONST_STRLEN( "FAILED" ), - CONST_STRLEN( "SUCCEEDED" ), - CONST_STRLEN( "REJECTED" ) - }; + minimumOptionalFieldsBufferSize += JOBS_API_STATUS_DETAILS_LENGTH + request.statusDetailsLength; + } - assert( ( ( size_t ) status ) < ARRAY_LENGTH( jobStatusString ) ); + return minimumOptionalFieldsBufferSize; +} - size_t start = 0U; - size_t minimumBufferSize = JOBS_API_STATUS_LENGTH + jobStatusStringLengths[ status ] + CONST_STRLEN( "\"}" ); +/** + * @brief Get the total length of the required fields in the + * Jobs_UpdateMsg request. + * + * @param request A JobsUpdateRequest_t containing the optional fields. + * @return size_t The buffer space required for the optional fields. + */ +static size_t getRequiredFieldsLength( JobsUpdateRequest_t request ) +{ + return JOBS_API_STATUS_LENGTH + strlen( jobStatusString[ request.status ] ) + CONST_STRLEN( "\"}" ); +} - if( ( expectedVersion != NULL ) && ( expectedVersionLength > 0U ) ) +/** + * @brief Check non-null optional fields in the Jobs_UpdateMsg request + * for validity. + * + * @param request A JobsUpdateRequest_t containing the optional fields. + * @return true Optional fields appear valid. + * @return false Optional fields are invalid. + */ +static bool areOptionalFieldsValid( JobsUpdateRequest_t request ) +{ + bool optionalFieldsValid = true; + + if( ( request.statusDetails != NULL ) && ( request.statusDetailsLength ) ) { - minimumBufferSize += JOBS_API_EXPECTED_VERSION_LENGTH + expectedVersionLength; + optionalFieldsValid &= ( JSONSuccess == JSON_Validate( request.statusDetails, request.statusDetailsLength ) ); } - bool writeFailed = bufferSize < minimumBufferSize; + return optionalFieldsValid; +} + +size_t Jobs_UpdateMsg( JobsUpdateRequest_t request, + char * buffer, + size_t bufferSize ) +{ + assert( ( ( size_t ) request.status ) < ARRAY_LENGTH( jobStatusString ) ); + + size_t start = 0U; + size_t minimumBufferSize = getRequiredFieldsLength( request ) + getOptionalFieldsLength( request ); + bool writeFailed = bufferSize < minimumBufferSize || !areOptionalFieldsValid( request ); - if( !writeFailed && ( jobStatusString[ status ] != NULL ) ) + if( !writeFailed ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_STATUS, JOBS_API_STATUS_LENGTH ); - ( void ) strnAppend( buffer, &start, bufferSize, jobStatusString[ status ], jobStatusStringLengths[ status ] ); + ( void ) strnAppend( buffer, &start, bufferSize, jobStatusString[ request.status ], strlen( jobStatusString[ request.status ] ) ); } - /* This is an optional field so do not fail if expected version is missing */ - if( !writeFailed && ( expectedVersion != NULL ) && ( expectedVersionLength > 0U ) ) + /* This is an optional field so do not fail if expected version is missing.*/ + if( !writeFailed && ( request.expectedVersion != NULL ) && ( request.expectedVersionLength > 0U ) ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_EXPECTED_VERSION, JOBS_API_EXPECTED_VERSION_LENGTH ); - ( void ) strnAppend( buffer, &start, bufferSize, expectedVersion, expectedVersionLength ); + ( void ) strnAppend( buffer, &start, bufferSize, request.expectedVersion, request.expectedVersionLength ); + } + + /* This is an optional field so do not fail if status details is missing.*/ + if( !writeFailed && ( request.statusDetails != NULL ) && ( request.statusDetailsLength > 0U ) ) + { + ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_STATUS_DETAILS, JOBS_API_STATUS_DETAILS_LENGTH ); + ( void ) strnAppend( buffer, &start, bufferSize, request.statusDetails, request.statusDetailsLength ); } if( !writeFailed ) diff --git a/test/unit-test/jobs_utest.c b/test/unit-test/jobs_utest.c index cf11ae7..bbfd006 100644 --- a/test/unit-test/jobs_utest.c +++ b/test/unit-test/jobs_utest.c @@ -878,65 +878,161 @@ void test_getStartNextPendingJobExecutionMsg_hasValidParameters( void ) /*Tests for getUpdateJobExecutionMsg */ void test_getUpdateJobExecutionMsg_hasNullExpectedVersion( void ) { - JobCurrentStatus_t status = Queued; char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; - size_t result = Jobs_UpdateMsg( status, NULL, 1U, buffer, TOPIC_BUFFER_SIZE ); + JobsUpdateRequest_t request = + { + Queued, + NULL, + 1, + "{\"key\": \"value\"}", + strlen( "{\"key\": \"value\"}" ) + }; - TEST_ASSERT_EQUAL( 19U, result ); + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); + + TEST_ASSERT_EQUAL( 54U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\",\"statusDetails\":\"{\"key\": \"value\"}\"}", buffer ); } void test_getUpdateJobExecutionMsg_hasZeroLengthExpectedVersion( void ) { - char * version = "1.0.1"; - JobCurrentStatus_t status = Queued; char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + 0, + "{\"key\": \"value\"}", + strlen( "{\"key\": \"value\"}" ) + }; - size_t result = Jobs_UpdateMsg( status, version, 0U, buffer, TOPIC_BUFFER_SIZE ); + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( 19U, result ); + TEST_ASSERT_EQUAL( 54U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\",\"statusDetails\":\"{\"key\": \"value\"}\"}", buffer ); +} + +void test_getUpdateJobExecutionMsg_hasNullStatusDetails( void ) +{ + char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + strlen( "1.0.1" ), + NULL, + strlen( "{\"key\": \"value\"}" ) + }; + + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); + + TEST_ASSERT_EQUAL( 45U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\",\"expectedVersion\":\"1.0.1\"}", buffer ); +} + +void test_getUpdateJobExecutionMsg_hasZeroLengthStatusDetails( void ) +{ + char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + strlen( "1.0.1" ), + "{\"key\": \"value\"}", + 0 + }; + + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); + + TEST_ASSERT_EQUAL( 45U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\",\"expectedVersion\":\"1.0.1\"}", buffer ); +} + +void test_getUpdateJobExecutionMsg_hasMalformedStatusDetails( void ) +{ + char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + strlen( "1.0.1" ), + "malformed-details", + strlen( "malformed-details" ) + }; + + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); + + TEST_ASSERT_EQUAL( 0U, result ); } void test_getUpdateJobExecutionMsg_hasTooSmallBufferSizeForRequiredParameters( void ) { - JobCurrentStatus_t status = Queued; char buffer[ 2 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + NULL, + 0, + NULL, + 0 + }; - size_t result = Jobs_UpdateMsg( status, NULL, 0U, buffer, 1 ); + size_t result = Jobs_UpdateMsg( request, buffer, 1 ); TEST_ASSERT_EQUAL( 0U, result ); } void test_getUpdateJobExecutionMsg_hasTooSmallBufferSizeForAllParameters( void ) { - char * version = "1.0.1"; - size_t versionLength = strlen( version ); - JobCurrentStatus_t status = Queued; - char buffer[ 25 ] = { 0 }; + char buffer[ 38 ] = { 0 }; + + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + strlen( "1.0.1" ), + "{\"key\": \"value\"}", + strlen( "{\"key\": \"value\"}" ) + }; - size_t result = Jobs_UpdateMsg( status, version, versionLength, buffer, 1 ); + size_t result = Jobs_UpdateMsg( request, buffer, 38 ); TEST_ASSERT_EQUAL( 0U, result ); } void test_getUpdateJobExecutionMsg_hasAllValidParameters( void ) { - char * version = "1.0.1"; - size_t versionLength = strlen( version ); - JobCurrentStatus_t status = Queued; char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + "1.0.1", + strlen( "1.0.1" ), + "{\"key\": \"value\"}", + strlen( "{\"key\": \"value\"}" ) + }; - size_t result = Jobs_UpdateMsg( status, version, versionLength, buffer, TOPIC_BUFFER_SIZE ); + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( 45U, result ); + TEST_ASSERT_EQUAL( 80U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\",\"expectedVersion\":\"1.0.1\",\"statusDetails\":\"{\"key\": \"value\"}\"}", buffer ); } void test_getUpdateJobExecutionMsg_hasRequiredValidParameters( void ) { - JobCurrentStatus_t status = Queued; char buffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 }; + JobsUpdateRequest_t request = + { + Queued, + NULL, + 0, + NULL, + 0 + }; - size_t result = Jobs_UpdateMsg( status, NULL, 0U, buffer, TOPIC_BUFFER_SIZE ); + size_t result = Jobs_UpdateMsg( request, buffer, TOPIC_BUFFER_SIZE ); TEST_ASSERT_EQUAL( 19U, result ); + TEST_ASSERT_EQUAL_STRING( "{\"status\":\"QUEUED\"}", buffer ); }