Automating OpenApi Spec Validation with Gradle and Spring Boot

Recently, the front-end team I’m on really saw how the back-end team was swamped. During the sprint retro, we expressed that we wanted to help out any way we could. So, with their help, we got set up in IntelliJ and back-end’s Spring Boot application. My team ended up getting wrapped up in our main projects. I put aside some time on my own to look at back-end’s backlog. After touching base with them and seeing where I could best help them, I picked up a technical debt ticket around automating the validation of the OpenAPI spec.

It was really a great adventure and learning opportunity! I had never worked before with Gradle and Spring Boot in any professional capacity, but I know my way around Gradle now. 😀

The requirements were:

  • the validation would be automated
  • it could be integrated into the CI/CD flow with GitHub actions

At first, I fell down a deep rabbit hole with trying to implement a custom Gradle Task with plain Java files. The Gradle 8.4 documentation actually pointed people to write them in Groovy or Kotlin, but I think I must have hit a random blog through a Google search, and gotten stuck there. I’m sure it could have worked, but I had already surpassed my initial timebox.

Then, I tried to solve it the simplest way, by using the springdoc-OpenAI Gradle plugin. But that required to run the application, and we wanted to avoid that in the CI/CD flow.

Finally, I went back to the original concept of custom Gradle Tasks, but kept it simple with plain Groovy. Then, I found the winning combination. A blog suggested validating the OpenAPI spec in test cases. The final solution was as follows:

  1. Writing custom Gradle Tasks in plain Groovy that executed test cases
  2. This case would manually generate the OpenAPI spec from the code
  3. Then executing a local shell script that loads a npm library to manually validate the generated spec.

It’s lightweight, it can run easily in a CI/CD flow, and implementing it with straight Groovy keeps the `build.gradle` file light.

“Final product!”

I added the custom task to the gradlew command in the appropriate GitHub Action workflow file.

I added the variables extraction.api-spec.json and springdoc.api-docs.path to the application.properties file.

build.gradle

tasks.register("validateOpenApiDocs", Exec) {

    group = "documentation"

    description = "Validates locally generated OpenApi spec"

    def stdout = new ByteArrayOutputStream()

    ignoreExitValue true

    doFirst() {

        println "Validating generated Open API docs..."

    }

    commandLine './validate-docs.sh'

    doLast() {

        ObjectMapper mapper = new ObjectMapper();

        JsonNode taskResult = mapper.readTree(stdout.toString())

        if (taskResult.valid.equals(false)) {

            println "FAILED"

            println taskResult.errors

        } else {

            println "OpenAPI spec validation passed!"

        }

    }

}

validate-docs.sh

#!/bin/bash

npx -p @seriousme/openapi-schema-validator validate-api docs.json

ApiSpecJsonFileExtractor.java

@SpringBootTest

@ActiveProfiles("test")

public class ApiSpecJsonFileExtractor {

  @Value("${extraction.api-spec.json}")

  String filename;

  @Value("${springdoc.api-docs.path}")

  String apiDocJsonPath;

<snip>

  MockMvc mvc;

  @BeforeEach

  public void setup() {

    mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();

  }

  @Test

  void extractApiSpecJsonFile() throws Exception {

    File file = new File(filename);

    Path filePath = file.toPath();

    if (file.exists()) {

      Assertions.assertThat(file.isFile()).isTrue();

    } else {

      Path path = file.getParentFile().toPath();

      if (Files.notExists(path)) {

        Files.createDirectory(path);

      }

      if (Files.notExists(filePath)) {

        Files.createFile(file.toPath());

      }

    }

    mvc.perform(MockMvcRequestBuilders.get(apiDocJsonPath))

        .andDo(

            result ->

                Files.write(file.toPath(), result.getResponse().getContentAsString().getBytes()));

  }

}

Making Bella

taken 2005

I made a film yesterday and put it online. It’s called Bella, after my late maternal grandmother’s nickname.

Backstory

I have been sitting on this concept for the past ten years. I was denied a chance to say goodbye and see her before she slept in Christ. So, I have been wanting to make sense of all the memories, the final things left untold, and living life without her around.

Returning to Egypt to visit my family, has not been the same since she left. There’s this hole now in Cairo, shaped after her. I spent a few days there in 2012 in my grandparents’ apartment. It didn’t bring me closer to her, as I had imagined. I left feeling the loss more intensely. That’s grieving, I suppose.

The idea

A month ago, I stood out in my balcony here in Stockholm. I hadn’t really been out there since I moved in. And as the bustle from the road, the balcony, and the sky converged in a moment: I remembered Bella.

And then I wondered if I could make that film about her, but here in my balcony.

Concept

standing in the balcony, reminiscing over Bella and remembering her, me narrating, with some of my own scoring and using a part of this song by the Saudi Arabian artist Hussain al-Jasmy. This song is about loss and when I first heard it, it become forever married to the memory of my grandmother.

Production

I shot the footage this morning with my Canon EOS on a monopod. Autofocus, standard lens, that’s it. I didn’t want to be distracted by the technical execution. That will come later, I know.

It was an artistic challenge to see how I could both record enough shots so that it is enough for the narration and so that it’s not much of the same. I tried to use different angles, mimicked some panning, and played with shadows on the walls.

Initial Editing

I went through the footage and made clips. I threw down them quickly onto the sequence in Premiere Pro, as quickly as possible. Some thoughts and pictures came back to me from when I was shooting, so I followed their lead. I played through the rough cut a few times to see how it feels.

I brought up a text editor and started writing the narration as it played through the sequence. In a way, I approached this like a broadcast news story rather than a film – record first and then evince the story from the footage.

After I laid down the initial rough cut, I looked at the shots and see how they worked with the script. I tweaked the script in Evernote, as I decided on the final sequence of shots. To avoid too much time re-recording audio later, I remembered a trick that a friend taught me a long time ago: use text clips in your editing software to play with the rough cut and editing process. This really helped me think through the shots before I touched the mic.

Scoring and Narration

I recorded the audio with the M-Audio M-Track soundcard and SM-56 vocal microphone. Adobe Audition Pro CC is my choice for post-processing and production. I really developed my skills in sound editing after this project.

Final Editing

This was definitely the most involved editing project I’ve undertaken, in that I had 4 audio tracks and several video tracks. After laying down all the audio, I took out the placeholder text and tweaked the edit further.

I wanted to avoid tropes with the opening sequence, so I got the idea to break up the introductory song clip with a piece of narration. Thank you, shower! It worked.

Favorite Moments

  • The intro
  • The shot where the narration says, “When I was growing up…” It was just a simple way to portray growing up.
  • The photo I picked of Bella
  • The flashback audio impression of Bella’s voice. I did it in one take and it’s still haunting me.

Lessons Learned

  1. Storyboarding and planning. Using the placeholder texts in the sequence and going through multiple edits made the whole process straightforward and intuitive.
  2. Good audio makes a difference. I really see the value of good-quality, well-edited, solid audio. It increases the production value exponentially.
  3. Keep the shots simple and cheap. They tend to express what you really want to say, without getting lost in the execution.

What’s Next

I’m brainstorming and ruminating over a film about my late friend Nancy. Stay tuned.

 

Verified by ExactMetrics