0

I'm trying to implement a web service API client using the guidlines i've found in this article: https://www.oreilly.com/library/view/designing-evolvable-web/9781449337919/ch09.html

As it comes to my understanding, article suggests to implement different API endpoints as Link classes, which know how to construct request, and parse response. I find it ideal because it helps me to decouple I/O, which is important, due to the fact that i'm using python, which has different sync/async IO implementations, which differs from project to project. For example, in it simplest form, this how it may look for openid connect .well-known endpoint:

import requests  # HTTP library

class WellKnownEndpoint:

    def __init__(self, issuer):
        self.issuer = issuer

    def create_request(self):
        return {
            "uri": self.issuer + ".well-known/openid-configuration",
            "method": "GET",
            "headers": {},
            "body": ""
        }
    
    def parse_response(self, status_code, headers, body):
        return json.loads(body)

Which can be used as:

wke = WellKnownEndpoint("https://example.com/")

request_data = wke.create_request()

response = requests.request(
    request_data["method"],
    request_data["uri"],
    headers=request_data["headers"],
    body=request_data["body"]
)

response_data = wke.parse_response(response.status_code, response.headers, response.text)

For security reasons, the openid-connect specification states that consumer of an API must validate that issuer, returned in the response for the request to .well-known endpoint, is equal to the issuer which was used in the request itself. Sounds simple enough, I can just add it to parse_response right?

...

def parse_response(self, status_code, headers, body):
    parsed_body = json.loads(body)
    if parsed_body["issuer"] != self.issuer:
        raise ValidationError("Issuer missmatch")
    return parsed_body
...

But does validation is the job for the data processing routine?

Isn't having some sort of validation in this case is too restrictive for a web service API library? Sometimes I just want a response, which I can observe, and it doesn't matter if something in this response is fishy.

How do you think it should be implemented? Do you think I'm overthinking it all?

I've tried adding separate method validate_response(response_data) on hypothetical WellKnownEndpoint class. But I think it's too much for a user of a library, to call one aditional method, and IMO it is prone to errors, it's hard to forget that you need to parse a response for a request, but it's easy to forget that response have to be validated.

Having an optional validator argument in constuctor which defaults to my implementation of a validator seems ok, but there is something about it which makes me think it's not the right thing to do, but what is this "something" - I don't know.

I'm sorry if it all look like nonsense and garbage, I'm new to the "actually thinking about software architecture" things.

1 Answer 1

1

How do you think it should be implemented? Do you think I'm overthinking it all?

You are definitely overthinking it, because you can have both.

For the casual user of your class, you offer the method parse_response:

...
def parse_response(self, status_code, headers, body):
    parsed_body = self.parse_response_unvalidated(status_code, headers, body)
    self.validate_response(parsed_body)
    return parsed_body
...

For the users that want more control and want to take the risk of using an unvalidated response, you offer them the methods parse_response_unvalidated and validate_response.

1
  • Love it. You're so right! Thank you very much
    – Javed
    Feb 8 at 7:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.