GSoC 2025: Enhancing OpenAPI Documentation Generation - Weekly Updates & Talk

Hi everyone! :waving_hand:

I’m Marvin Sukumar, an undergraduate student studying computer science in Mumbai, India. I’m thrilled to be selected as a Google Summer of Code 2025 contributor with OpenMRS! I thank my mentors @chibongho and @mherman22 for consistently guiding me throughout.

:memo:Project Overview

My project is focused on replacing the current OpenMRS Standalone REST documentation generation process with a modern, automated system that generates OpenAPI 3.0 specifications at build-time.

The idea is to reduce the manual, error-prone approach currently used, and instead build a custom Maven plugin that extracts and constructs API specs using:

  • Annotation processing
  • Static code analysis
  • Javadoc integration
  • And OpenMRS-specific REST handler support

Ultimately, the goal is to provide an accurate, complete, and maintainable OpenAPI 3.0 specification

:spiral_calendar: Updates and Progress

I’ll use this thread to:

  • Post weekly updates
  • Share blockers and discussions
  • Get feedback from mentors and the community
  • Keep the documentation trail open and transparent

Feel free to ask questions, give suggestions, or help me debug tricky areas! I’d love to collaborate closely with everyone here. :raising_hands:

I plan on making weekly blog posts, preferably on Notion but any inputs or suggestions would be appreciated.

Resources & Links

Notion Doc: Notion

Proposal: Marvin_GSOC_Proposal.pdf - Google Drive

Project Description: Enhancing OpenAPI Documentation Generation - Projects - OpenMRS Wiki

cc: @ibacher @dkayiwa @grace @jayasanka

5 Likes

Hello everyone!

I have written my first blog pertaining to all the progress I have had in my first week post GSoC. I hope it is clear and informative, any suggestions or feedback would be highly appreciated.

Thank You!

Link: (Your connected workspace for wiki, docs & projects | Notion)

1 Like

:sparkles:Major Project Progress Update - 01

Upon initiating my GSoC work, I began by tackling the task of extracting Javadoc comments present at both class and method levels within each REST resource. After thorough exploration, I narrowed down the approach to two viable options: the Doclet API, which operates by invoking the javadoc tool to process source code, and JavaParser, which parses .java files into an Abstract Syntax Tree (AST), enabling traversal and structured analysis of code elements.

After evaluating the trade-offs (which I documented extensively), I chose to proceed with JavaParser due to its lightweight footprint, developer ergonomics, and seamless programmatic integration into a Maven plugin environment.

The next and most substantial challenge I encountered relates to accurately capturing the REST API representations (reps) defined in the webservices.rest module—specifically, for GET operations on resource classes. OpenMRS supports multiple representations like default, full, ref, and custom, each returning different sets of properties. To generate high-fidelity OpenAPI documentation, it’s essential to statically determine what each representation returns for every resource. This level of documentation is invaluable for new contributors, integrators, and external developers consuming the APIs.

Historically, the OpenMRS REST module relied on the getGETModel() method to define representation content, but this was inconsistent and unreliable across resources. To overcome this, I’m now working on invoking the getRepresentationDescription(Representation rep) method reflectively at build time. This method returns a DelegatingResourceDescription, which explicitly defines the fields and sub-resources included in each representation type.

However, this approach introduces a significant technical caveat: during Maven’s build lifecycle, we lack access to the Spring application context and many of the supporting OpenMRS services that would normally be injected at runtime. As a result, invoking methods that internally rely on these services (even indirectly) can result in exceptions, NullPointerExceptions, or incomplete behavior.

For instance, while testing with PatientResource1_8, I was able to instantiate the class using its default no-args constructor. However, when calling getRepresentationDescription() on it, the method fails because it references static members like RestConstants, which in turn may rely on Spring-managed beans or modules not yet initialized in the build phase. To verify this, I commented out the problematic lines, and the method successfully returned the expected properties for both default and full representations.

[INFO] ------------< org.openmrs.module:webservices.rest-omod-1.8 >------------
[INFO] Building Rest Web Services 1.8 OMOD 2.50.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- openapi-generator:1.0-SNAPSHOT:openapi (default-cli) @ webservices.rest-omod-1.8 ---        
[INFO] === OPENAPI GENERATION STARTED ===
[INFO] ClassLoader setup complete: 22 URLs
[INFO] Loaded class: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] === Testing PatientResource Instance Creation ===
[INFO] Attempting to create PatientResource1_8 instance using normal constructor...
[INFO] ? SUCCESS! PatientResource1_8 instance created successfully!
[INFO] Instance class: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] Instance toString: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8@77f905e3
[INFO] --- Testing Basic Method Calls ---
[INFO] ? toString() works: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8@77f905e3
[INFO] ? getClass() works: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] ? Found 30 declared methods
[INFO] ? Found getRepresentationDescription method: public org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8.getRepresentationDescription(org.openmrs.module.webservices.rest.web.representation.Representation)
[INFO] === Testing getRepresentationDescription Method ===
[INFO] ? DEFAULT result: org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription@2b2954e1
[INFO] ? FULL result: org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription@58d6e55a
[INFO] === Extracting Schema from DEFAULT ===
[INFO] Found 5 properties:
[INFO]   - uuid -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@235d659c
[INFO]   - display -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@4232b34a
[INFO]   - identifiers -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@2da16263
[INFO]   - person -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@f5ce0bb
[INFO]   - voided -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@47e51549
[INFO] === Extracting Schema from FULL ===
[INFO] Found 6 properties:
[INFO]   - uuid -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@101a461c
[INFO]   - display -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@360e9c06
[INFO]   - identifiers -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@5ebffb44
[INFO]   - person -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@311ff287
[INFO]   - voided -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@7377781e
[INFO]   - auditInfo -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@31db34da
[INFO] ==============
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

I also experimented with unsafe object allocation (via sun.misc.Unsafe or ObjectInputStream) to instantiate the class without triggering constructors or initialization blocks, but this yielded similar issues due to static dependencies being evaluated during method execution.

I intend to share the exact stack traces and points of failure in follow-up discussions, but I wanted to flag this design tension early—especially if there’s precedent or tooling within the OpenMRS ecosystem for solving this kind of build-time reflection over runtime-dependent logic.

Despite the complexity, I’m optimistic. Progress has been steady and productive, and I’m confident that with the right design abstractions, this plugin will bring clarity, consistency, and significant value to the way OpenMRS exposes its REST API documentation.

This is the architecture of the whole documentation generator that we have planned (Thank you @mherman22 for such a detailed graph)

Thank you again for the support and guidance—I’m excited to keep pushing forward and deliver something the community will be proud of.

2 Likes

Hello everyone!

Just a quick check in considering I am at a point where I need the expertise to decide my further decisions.

For context, currently I have made a maven plugin that implements a URLClassLoader to load the necessary resource classes from the omod where it is configured in the pom.xml file, it scans all the classes to find if it has @Resource annotation, and we instantiate that class with a no-args constructor to invoke it’s getRepresentationDescription() method so we can extract that particular class’s properties that it returns for its ‘default’ and ‘full’ representations.

Currently the biggest decision I have to take is regarding support for various omod versions present in the webservices-REST module. Till what platform versions should this plugin ideally support? As things will go out of hand very quickly if we try to document multiple versions of the same resource.

The way I instantiate the resource classes is through a method where I disable the contextEnabled variable so that I can do that without context. Is this method valid or is this not how I should go about trying to invoke the getRepresentationDescription() method? I would be happy to elaborate on any of the above points to have a clearer picture.

cc : @ibacher @dkayiwa @raff @mherman22 @chibongho

We have just made the webservices module require platform 2.4 as the lowest version. Does it help you in any way? Jira

I seem to get entangled in these version numbers, am I right in saying that the platform versions (i.e. the versions present in the REST module) are the OpenMRS core versions?

So with this PR in place we now support only versions 2.4 and above right?

The reason for my confusion is the REST module contains most of the resource files in the older omod directories of 1.8 , 1.9 etc. and the newer ones have significantly lesser. So does this mean the resources present in the older versions need not be documented? Sorry for my ignorance but I’ll move accordingly once I get to know what is right

Correct.

Correct and it is already merged.

We have gotten rid of those directories and left with only those required to support from openmrs core version 2.4.0 and above.

Got it! It’s a really big PR so I’ll take a day to see what exactly happened

But going through the changes I still see these resource files exist inside omod-2.4, for example there exists both PatientResource1_8 and PatientResource1_9 so it doesn’t make sense to document both right? That is a big question I have

And I would appreciate any OpenMRS specific advices that might help me to interact with these methods in build time, as I said before that currently I use the method where I disable contextEnabled to disable all context so I can proceed with invoking it.

Generally PatientResource1_9 extends PatientResource1_8, but only one of them will be active at a time, so the goal would be to document the active one, but some of its properties will be defined in the inactive one.

This is what the @OpenmrsProfile annotation on the class does: define which versions of openmrs-core the class should be actively defined for.

If we’re doing this statically, then we may need a different version of documentation for each supported version of core, e.g., a 2.4.x, 2.5.x, 2.6.x, 2.7.x, etc. version of documentation.

This won’t be known to us at runtime as I understand it? Is there a method to detect the OpenMRS core version at runtime and make this plugin version-aware?

This is the first time encountering this annotation as I haven’t seen them in any of the REST resource classes I have worked on, although in the @Resource annotation there does exist a parameter of supportedOpenmrsVersions, is that something relevant ?

How do you envision this works? We generate a json file for each version and fetch the json according to the core version that is detected at runtime?

is that something relevant ?

Oh, sorry, yeah, the @Resource annotation is backed by that same code.

This won’t be known to us at runtime as I understand it?

It’s known at runtime easily, but a little more tricky at build time.

We generate a json file for each version and fetch the json according to the core version that is detected at runtime?

If we’re doing the documentation generation at build time, then that’s probably the most straight-forward approach. One reason the current documentation is generated at runtime rather than at build time is to avoid these kinds of issues. (Another is that other modules can contribute REST endpoints, so the Swagger docs just generated from the REST module will never be comprehensive).

Apologies I did mean build time, alright I’ll pivot to making this work, do you think this approach of making it version-aware makes more sense?

Of course. I think my immediate plan is nailing the default and full representations which are generated, then I have to make a decision on whether to keep it static or version-aware and continue from there

I don’t know how else we’ll successfully generate correct Swagger docs for various versions of the platform without it being version aware, but maybe that’s something that can be hammered out in the future.

1 Like

After pulling the change to the latest commits in the REST module I am being met with an error whenever I run mvn clean install or mvn clean package on omod-2.4 or the whole REST module due to a particular test failing. Is anyone else facing the same issue?

Judging by the error logs it is the TaskActionController2_4Test file, here’s the log:

[INFO] Running org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test
[ERROR] Tests run: 11, Failures: 4, Errors: 0, Skipped: 0, Time elapsed: 0.395 s <<< FAILURE! - in org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test
[ERROR] shouldShutdownTask  Time elapsed: 0.077 s  <<< FAILURE!
java.lang.AssertionError:

Expected: not a collection containing <[TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0]>
but: was <[[TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0]]>
at org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test.shouldShutdownTask(TaskActionController2_4Test.java:78)

[ERROR] shutdownTask_shouldDoNothingIfTaskAlreadyShutdown  Time elapsed: 0.018 s  <<< FAILURE!
java.lang.AssertionError:

Expected: not a collection containing <[TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0]>
but: was <[[TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0]]>
at org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test.shutdownTask_shouldDoNothingIfTaskAlreadyShutdown(TaskActionController2_4Test.java:83)

[ERROR] shouldScheduleTask  Time elapsed: 0.024 s  <<< FAILURE!
java.lang.AssertionError:

Expected: a collection containing <[TaskDefinition  id=2 name=Equipment Maintenance class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3600 secondsUntilNext=0]>
but: mismatches were: [was <[TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0]>, was <[TaskDefinition  id=1 name=Nursing Education class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=36000 secondsUntilNext=0]>]
at org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test.shouldScheduleTask(TaskActionController2_4Test.java:60)

[ERROR] shouldDeleteTask  Time elapsed: 0.032 s  <<< FAILURE!
java.lang.AssertionError:

Expected: not a collection containing <[TaskDefinition  id=1 name=Nursing Education class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=36000 secondsUntilNext=0]>
but: was <[[TaskDefinition  id=1 name=Nursing Education class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=36000 secondsUntilNext=0], [TaskDefinition  id=2 name=Equipment Maintenance class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3600 secondsUntilNext=0], [TaskDefinition  id=3 name=Nurse Precepting class=org.openmrs.scheduler.tasks.TestTask startTime=null repeatInterval=3640 secondsUntilNext=0], [TaskDefinition  id=4 name=Chronic Care class=null startTime=null repeatInterval=18000 secondsUntilNext=0]]>
at org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4.TaskActionController2_4Test.shouldDeleteTask(TaskActionController2_4Test.java:119)

cc: @dkayiwa @raff @ibacher

After understanding the Test file and looking at the various files related to it, I inferred that there exist different wrapper classes i.e. MockTaskServiceWrapper and TaskServiceWrapper2_4.

When the test for TaskActionController1_8 runs, it sets up the TaskActionResource1_8 to run on the MockTaskServiceWrapper(), while we use TaskServiceWrapper2_4 in the 2_4 controller file. Since this file extends the 1_8 test the collision of different wrapper classes is causing the errors, and when I re implement the TaskActionResource to use the 2_4 wrapper class everything is solved. Should I push this solution?

Do you have a pull request that we can look at, to fully understand what you are doing?

Opened one just now, you can have a look:

RESTWS-984: Error in TaskActionController when clean installing the REST module by capernix · Pull Request #665 · openmrs/openmrs-module-webservices.rest

@raff can you specify the class name that would help me get the required application context for the plugin? Much appreciated

@raff @dkayiwa Following up my discussion from the platform team call, what are the steps I should follow in order to start maintaining the OpenAPI plugin in its own OpenMRS repository? Do I have to register it in the OpenMRS maven repository? If you can point me to the direction of any documentation related to this that’ll be great

@capernix please have a look at BaseContextSensitiveTest

It creates a spring application context for OpenMRS based on @ContextConfiguration(locations = { “classpath:applicationContext-service.xml”, “classpath*:moduleApplicationContext.xml”, “classpath*:TestingApplicationContext.xml” })

It also configures DB connection to use an in-memory H2 DB.

I’m not sure, if you can use this class directly in the maven plugin easily by loading projects’ classes. The maven surefire plugin does it somehow, but it might not be straightforward.

However, I can see an easier approach by not implementing a plugin, rather creating a maven submodule within webservices.rest, which can be specified to run against any version of openmrs–core via a property and then within this module create a test class extending BaseWebModuleContextSensitiveTest and generate docs while running a test. This way you can use maven surefire plugin to handle class loading and BaseWebModuleContextSensitiveTest to create OpenMRS application context with all relevant REST resources loaded. The test can put docs in any location in the module to be used later during build time.

Regarding putting a plugin in a separate repository… For now you can create a repo under your github account. We will put it under OpenMRS when ready. You also don’t need to put jars in OpenMRS maven repo. During development you can just call mvn install to put them in your local maven repo and use in local builds.

2 Likes