Skip to content

Conversation

@sighphyre
Copy link
Member

Cleans up some of the parsing (and lack thereof) code higher up in the flow so that it occurs on IO boundaries rather than lurking like some suspicious octopus in the repository layers.

I've done as much as I have the courage to do here, some of this is cursed but I don't want to break things in the wild

}

export type ApiResponse = ClientFeaturesDelta | ClientFeaturesResponse;
export type ApiResponse =
Copy link
Member Author

Choose a reason for hiding this comment

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

We have no natural discriminator on this but we control the parsing code so thanos_fine_ill_do_it_myself.png

Copy link
Member

Choose a reason for hiding this comment

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

If you'd like to avoid the type property injection you could also use TS type predicates.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't have strong feelings to be honest. In the spirit of getting a bunch of stuff tested I'm going to merge this. But if you do have feelings here absolutely give me a shout and I'll move this to type predicates in another PR

Array.isArray(delta.events)
) {
return delta as ClientFeaturesDelta;
export const parseApiResponse = (data: unknown): ApiResponse => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Parsing deltas is more or less unchanged. I think we should do more but this is probably robust enough since this originates in either Edge or Unleash. The full client response gets some actual validation rather than yeeting any which was what was previously happening

} else if ('features' in data && Array.isArray(data.features)) {
return { ...data, type: 'full' } as ClientFeaturesResponse & { type: 'full' };
}
throw new Error(
Copy link
Member Author

Choose a reason for hiding this comment

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

This is trapped in either the fetcher, resulting in an error message or the stream code, which triggers a failover to polling

this.segments = this.createSegmentLookup(response.segments);
break;
}
default: {
Copy link
Member Author

Choose a reason for hiding this comment

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

Since we control the parse code, this can never occur. If someone yeets in any any from the outside, the parse code will reject before we get here. So this is just compiler sugar for warning a future developer when they break this invariant

Copy link
Member

@nunogois nunogois Dec 2, 2025

Choose a reason for hiding this comment

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

My instinct would be to assume full as default, but your approach is more explicit and probably better.

if (isDelta(response)) {
  // do delta things

  return;
}

// do full things

Copy link
Member Author

Choose a reason for hiding this comment

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

This should be a system invariant now. I want "guessing what shape stuff is" to stay in the parsing layers so that we only guess in one place and don't litter our code with poorly understood branches


if (content && this.notEmpty(content)) {
await this.save(content, false);
await this.save(parseApiResponse(content), false);
Copy link
Member Author

Choose a reason for hiding this comment

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

We have two choices here. Either we de-structure and & with {'type': 'full'} or we throw this at the parsing code and let it figure it. Given that this is user defined, I'm going with option 2

);

return obj;
private convertToMap(features: FeatureInterface[] | undefined | null): FeatureToggleData {
Copy link
Member Author

Choose a reason for hiding this comment

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

This isn't relevant to this PR but gosh this function is just cursed. I think this is clearer and functionally the same thing (it's still cursed, it's just clear why it's cursed)

Copy link
Member

@nunogois nunogois Dec 2, 2025

Choose a reason for hiding this comment

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

Would it be clearer if we early returned in case of empty or invalid features?

if (!features?.length) return {};

// do the thing

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, seems like a reasonable thing to do, patched.

Honestly though, this function needs... well... the reasons for its existence need to go away before we can make this code sane

return;
}
await this.options.onSave(data, true);
await this.options.onSave(parseApiResponse(data), true);
Copy link
Member Author

Choose a reason for hiding this comment

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

Aw yiss, parse at IO boundary so we don't let goop into the heart of our system. I think this is safe because the parse code isn't a deep check and doesn't reject anything that wouldn't break later anyway

Copy link
Member

@nunogois nunogois left a comment

Choose a reason for hiding this comment

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

LG!

@github-project-automation github-project-automation bot moved this from New to Approved PRs in Issues and PRs Dec 2, 2025
Base automatically changed from chore/make-client-spec-tests-work to main December 3, 2025 08:28
@coveralls
Copy link

Pull Request Test Coverage Report for Build 19887381135

Details

  • 34 of 40 (85.0%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.03%) to 89.772%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/feature.ts 6 8 75.0%
src/repository/index.ts 26 30 86.67%
Totals Coverage Status
Change from base Build 19887316754: -0.03%
Covered Lines: 1183
Relevant Lines: 1268

💛 - Coveralls

@sighphyre sighphyre merged commit b20061d into main Dec 3, 2025
5 checks passed
@sighphyre sighphyre deleted the chore/parse-safety-for-api branch December 3, 2025 08:34
@github-project-automation github-project-automation bot moved this from Approved PRs to Done in Issues and PRs Dec 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants