Skip to content

Replacing vets-json-schema with rails validation for COE Form Submissions#27781

Open
jesse-b-wilcore wants to merge 12 commits intomasterfrom
2489-implement-rails-validation-for-coe-form-submission-replacing-vets-json-schema
Open

Replacing vets-json-schema with rails validation for COE Form Submissions#27781
jesse-b-wilcore wants to merge 12 commits intomasterfrom
2489-implement-rails-validation-for-coe-form-submission-replacing-vets-json-schema

Conversation

@jesse-b-wilcore
Copy link
Copy Markdown
Contributor

@jesse-b-wilcore jesse-b-wilcore commented Apr 15, 2026

Keep your PR as a Draft until it's ready for Platform review. A PR is ready for Platform review when it has a teammate approval and tests, linting, and settings checks pass CI. See these tips on how to avoid common delays in getting your PR merged.

Summary

  • replace vets-json-schema with rails validation

Related issue(s)

2489

Testing done

  • Unit tests

Screenshots

Note: Optional

What areas of the site does it impact?

(Describe what parts of the site are impacted andifcode touched other areas)

  • Coe/saved claims

Acceptance criteria

  • form_matches_schema overridden in CoeClaim with Rails validation
  • All required fields validated for presence
  • Field types validated (strings, booleans, nested objects)
  • Format validation for email, phone numbers, dates, addresses, etcexpected
  • I added a screenshot of the developed feature

Requested Feedback

(OPTIONAL)What should the reviewers know in addition to the above. Is there anything specific you wish the reviewer to assist with. Do you have any concerns with this PR, why?

@jesse-b-wilcore jesse-b-wilcore requested review from a team as code owners April 15, 2026 20:21
@jesse-b-wilcore jesse-b-wilcore marked this pull request as draft April 15, 2026 20:21
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

1 Warning
⚠️ This PR changes 428 lines of code (not counting whitespace/newlines, comments, or test files).

In order to ensure each PR receives the proper attention it deserves, we recommend not exceeding
200. Expect some delays getting reviews.

File Summary

Files

  • .github/CODEOWNERS (+6/-0)

  • app/models/concerns/coe_claim_form_validation.rb (+333/-0)

  • app/models/saved_claim/coe_claim.rb (+34/-4)

  • config/features.yml (+1/-1)

  • lib/coe/service_branch.rb (+38/-0)

  • lib/common/validations_patterns.rb (+11/-0)

    Note: We exclude files matching the following when considering PR size:

    *.csv, *.json, *.tsv, *.txt, *.md, Gemfile.lock, app/swagger, modules/mobile/docs, spec/fixtures/, spec/support/vcr_cassettes/, modules/mobile/spec/support/vcr_cassettes/, db/seeds, modules/vaos/app/docs, modules/meb_api/app/docs, modules/appeals_api/app/swagger/, *.bru, *.pdf, modules/*/spec/fixtures/*, modules/*/spec/factories/*, modules/*/spec/**/*.rb, spec/**/*.rb, modules/*/docs/**/*.yaml, modules/*/docs/**/*.yml, modules/*/app/docs/**/*.yaml, modules/*/app/docs/**/*.yml
    

Big PRs are difficult to review, often become stale, and cause delays.

Generated by 🚫 Danger

Comment thread app/models/concerns/coe_claim_form_validation.rb Outdated
Comment thread app/models/concerns/coe_claim_form_validation.rb Outdated
Comment thread app/models/concerns/coe_claim_form_validation.rb
Comment on lines -29 to +32
{ status: e.status, messsage: e.message, body: e.body })
{ status: e.status, message: e.message, body: e.body })
else
Rails.logger.error('LGY API returned error', { status: e.status, messsage: e.message, body: e.body })
Rails.logger.error('LGY API returned error', { status: e.status, message: e.message, body: e.body })
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixing a typo but unrelated to this ticket

Comment on lines -13 to -20
# Otherwise, submit the claim to the LGY API
else
Rails.logger.info('Begin COE claim submission to LGY API', guid:)
response = lgy_service.put_application(payload: prepare_form_data)
Rails.logger.info('COE claim submitted to LGY API', guid:, reference_number: response['reference_number'])

process_attachments!
response['reference_number']
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else statement was unnecessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason this feels like a very, "FE dev" thing to do, but I like it. I would have stuffed it all into a class constant, which would've made reading the source code more difficult

Comment thread lib/coe/service_branch.rb
module Coe
module ServiceBranch
CONFIG_PATH = Rails.root.join('config', 'coe', 'service_branch.json')
TABLE = JSON.parse(CONFIG_PATH.read).freeze
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel like there may be a better name than table to describe the json structure but couldn't think of it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming things is hard

it 'sends the right data to LGY' do
# rubocop:disable Layout/LineLength
coe_claim = create(:coe_claim, form: '{"relevantPriorLoans":[{"dateRange":{"from":"2017-01-01T00:00:00.000Z","to":""},"propertyAddress":{"propertyAddress1":"234","propertyAddress2":"234","propertyCity":"asdf","propertyState":"AL","propertyZip":"11111"},"propertyOwned":false,"vaLoanNumber":"123123123123","intent":"IRRRL"},{"dateRange":{"from":"2010-01-01T00:00:00.000Z","to":"2011-01-01T00:00:00.000Z"},"propertyAddress":{"propertyAddress1":"939393","propertyAddress2":"234","propertyCity":"asdf","propertyState":"AL","propertyZip":"11111"},"propertyOwned":true,"vaLoanNumber":"123123123123","intent":"REFI"}],"vaLoanIndicator":true,"periodsOfService":[{"serviceBranch":"Air Force","dateRange":{"from":"2000-01-01T00:00:00.000Z","to":"2010-01-16T00:00:00.000Z"}}],"identity":"ADSM","contactPhone":"2223334444","contactEmail":"[email protected]","fullName":{"first":"Eddie","middle":"Joseph","last":"Caldwell"},"dateOfBirth":"1933-10-27","applicantAddress":{"country":"USA","street":"123 ANY ST","city":"ANYTOWN","state":"AL","postalCode":"54321"},"privacyAgreementAccepted":true}')
coe_claim = create(:coe_claim, form: '{"relevantPriorLoans":[{"dateRange":{"from":"2017-01-01T00:00:00.000Z","to":""},"propertyAddress":{"propertyAddress1":"234","propertyAddress2":"234","propertyCity":"asdf","propertyState":"AL","propertyZip":"11111"},"propertyOwned":false,"vaLoanNumber":"123123123123","intent":"IRRRL"},{"dateRange":{"from":"2010-01-01T00:00:00.000Z","to":"2011-01-01T00:00:00.000Z"},"propertyAddress":{"propertyAddress1":"939393","propertyAddress2":"234","propertyCity":"asdf","propertyState":"AL","propertyZip":"11111"},"propertyOwned":true,"vaLoanNumber":"123123123123","intent":"REFI"}],"vaLoanIndicator":true,"periodsOfService":[{"serviceBranch":"AF","dateRange":{"from":"2000-01-01T00:00:00.000Z","to":"2010-01-16T00:00:00.000Z"}}],"identity":"ADSM","contactPhone":"2223334444","contactEmail":"[email protected]","fullName":{"first":"Eddie","middle":"Joseph","last":"Caldwell"},"dateOfBirth":"1933-10-27","applicantAddress":{"country":"USA","street":"123 ANY ST","city":"ANYTOWN","state":"AL","postalCode":"54321"},"privacyAgreementAccepted":true}')
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to update these as the validations were causing failure in other tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow that is ugly. I don't blame you for just changing "Air Force" -> "AF" to get this one over the line, but it would be good to have a factory function to create different form objects. Would you mind creating a ticket for that, we can throw it on our backlog to do later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah ill make a ticket for it

Comment on lines -130 to -151
# values from the FE for military_branch are:
# ["Air Force", "Air Force Reserve", "Air National Guard", "Army", "Army National Guard", "Army Reserve",
# "Coast Guard", "Coast Guard Reserve", "Marine Corps", "Marine Corps Reserve", "Navy", "Navy Reserve"]
# these need to be formatted because LGY only accepts [ARMY, NAVY, MARINES, AIR_FORCE, COAST_GUARD, OTHER]
# and then we have to pass in ACTIVE_DUTY or RESERVE_NATIONAL_GUARD for service_type
military_branch = service_info['serviceBranch'].parameterize.underscore.upcase
service_type = 'ACTIVE_DUTY'

# "Marine Corps" must be converted to "Marines" here, so that the `.any`
# block below can convert "Marine Corps" and "Marine Corps Reserve" to
# "MARINES", to meet LGY's requirements.
military_branch = military_branch.gsub('MARINE_CORPS', 'MARINES')

%w[RESERVE NATIONAL_GUARD].any? do |service_branch|
next unless military_branch.include?(service_branch)

index = military_branch.index('_NATIONAL_GUARD') || military_branch.index('_RESERVE')
military_branch = military_branch[0, index]
# "Air National Guard", unlike "Air Force Reserve", needs to be manually
# transformed to AIR_FORCE here, to meet LGY's requirements.
military_branch = 'AIR_FORCE' if military_branch == 'AIR'
service_type = 'RESERVE_NATIONAL_GUARD'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I neglected to include information in the ticket about feature toggles (sorry), this stuff will need to be reinstated, and an if clause introduced, to use the COE::ServiceBranch helper

'vaLoanIndicator' => true,
'periodsOfService' => [{
'serviceBranch' => 'Air National Guard',
'serviceBranch' => 'ANG',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that you thought to update the swagger documentation here. I would have forgotten this.

Is the rest of the incoming object really identical, from current version to the stuff that the FE guys have rebuilt? Seems like we should check this (although i am totally fine with putting it into a separate PR)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah ill make a follow up ticket

Copy link
Copy Markdown
Contributor

@AdamKing0126 AdamKing0126 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good so far. Once you insert logic for the feature toggle, I will approve.

Copy link
Copy Markdown
Contributor

@AdamKing0126 AdamKing0126 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, no notes. I appreciate your flexibility wrt feature flag stuff.

@jesse-b-wilcore jesse-b-wilcore marked this pull request as ready for review April 22, 2026 22:03
@jesse-b-wilcore jesse-b-wilcore requested a review from a team as a code owner April 22, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants