3ric-T
3ric-T

Reputation: 41

How to document a Map with Enum as key entry?

I am doing documentation for a REST service that returns this kind of JSON:

{
  "CKM_RSA_PKCS_OAEP" : {
    "keyType" : "RSA",
    "canGenerateKey" : false,
    "canEncryptDecrypt" : true,
    "canSignVerify" : false,
    "canWrapSecretKeys" : true,
    "canWrapPrivateKeys" : false,
    "canDerive" : false,
    "canDigest" : false,
    "minKeySize" : 512,
    "maxKeySize" : 16384
  },
  "CKM_SHA384" : {
    "canGenerateKey" : false,
    "canEncryptDecrypt" : false,
    "canSignVerify" : false,
    "canWrapSecretKeys" : false,
    "canWrapPrivateKeys" : false,
    "canDerive" : false,
    "canDigest" : true,
    "minKeySize" : 0,
    "maxKeySize" : 0
  }
}

This represents a Map<Mechanism, MechanismInfo> where Mechanism is an enum and MechanismInfo a POJO.

I want to document only the fields of the MechanismInfo class regardless of Mechanism with the constraint that keyType attribute can be null and then not present in the response. All other attributes are either integers or boolean and will have a default value.

Here are a part of my Junit 5 test:

this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext)
  .configureClient()
  .filter(documentationConfiguration(restDocumentation)
    .operationPreprocessors()
    .withResponseDefaults(prettyPrint()))
  .build();
(...)
webTestClient
  .get().uri("/tokens/{id}/mechanisms", TOKEN_TEST)
  .exchange()
  .expectStatus().isOk()
  .expectBody(new ParameterizedTypeReference<Map<Mechanism, MechanismInfo>>() {
  })
  .consumeWith(document(
   "get_mechanisms"
  ));

Could someone help me to do this?

Upvotes: 0

Views: 403

Answers (1)

3ric-T
3ric-T

Reputation: 41

As i finally found how to address this, I post how I did. The main point is the modification of the content of the request before its documentation as described in Spring REST Docs documentation.
So, I created my own OperationPreprocessor to modify the response, taking advantage of the built-in ContentModifyingOperationPreprocessor which requires only to implement the ContentModifier interface. The idea was to be able to extract a unique value that I knew it was always there.

Here is what it looks like:

private final class MechanismsExtract implements ContentModifier {
  @Override
  public byte[] modifyContent(byte[] originalContent, MediaType contentType) {
    ObjectMapper objectMapper = new ObjectMapper();
    try {
      Map<String, LinkedHashMap> map = objectMapper.readValue(originalContent, Map.class);
      final LinkedHashMap mechanismInfo = map.entrySet().stream()
        .filter(
          key -> key.getKey().equals(Mechanism.CKM_RSA_PKCS_KEY_PAIR_GEN.name())
        )
        .map(
          Map.Entry::getValue
        )
        .findFirst()
        .orElseThrow();

      return objectMapper.writeValueAsBytes(mechanismInfo);
    } catch (IOException ex) {
      return originalContent;
    }
  }
}

then when documenting:

(...)
  .consumeWith(document(
    "get_mechanisms",
    // Output is limited to chosen mechanisms for documentation readability reasons
    Preprocessors.preprocessResponse(
      new ContentModifyingOperationPreprocessor(
        new MechanismsExtract()
      )
    ),
    relaxedResponseFields(
      fieldWithPath(NAME).description(DESCRIPTION),
      (...)
    )
    (...)
  )
(...)

Upvotes: 0

Related Questions