Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing a Problem Document from JSON #20

Open
sazzer opened this issue Dec 20, 2020 · 7 comments
Open

Parsing a Problem Document from JSON #20

sazzer opened this issue Dec 20, 2020 · 7 comments

Comments

@sazzer
Copy link

sazzer commented Dec 20, 2020

This package works great for the server-side, but it would be great if it were usable on the client-side as well by being able to parse an incoming JSON document into a ProblemDocument, complete with access to any extension values in a safe manner.

Cheers

@AlexZeitler
Copy link
Contributor

@sazzer Thanks for using the package.

Could you please explain what and how you would expect to use it?

Would you be willing to send a PR?

@AlexZeitler
Copy link
Contributor

@sazzer I think https://github.com/badgateway/ketting#introduction can do this for you.

@sazzer
Copy link
Author

sazzer commented Apr 12, 2021 via email

@AlexZeitler
Copy link
Contributor

AlexZeitler commented Jun 8, 2021

@sazzer You can follow the progress for a parser: https://github.com/PDMLab/http-problem-details-parser

Looking forward to your feedback.

@robincafolla
Copy link

robincafolla commented Jun 14, 2021

I'm using this library on the frontend in a couple projects. This is how I'm handling them (I'm not using it on the backend, which is PHP in my case)

class Api {

  get(url, queryParams = []) {
    let urlWithParams = url + params(queryParams);

    return this.handleProblem(fetch(urlWithParams, {
      credentials: 'same-origin',
      method: 'GET',
      mode: 'same-origin',
      cache: 'no-cache',
    }));
  }

  /**
   * @param {Promise} result
   * @return {Promise}
   */
  handleProblem = (result) => {
    return new Promise(((resolve, reject) => {
      result.then((response) => {
        // If the data returned is a problem
        if (response.headers.get("content-type") === "application/problem+json" ) {
          const clonedResponse = response.clone();
          response.json()
            .then((json) => {
              // If we didn't request the problem's actual URL
              if (json.type && response.url !== json.type) {
                const extensions = json.extensions || null;
                const problem = new ProblemDocument(json, extensions);
                reject(problem);
              } else {
                resolve(clonedResponse);
              }
            })
            .catch((error) => {
              log('json error', error);
              reject(error)
            });
        } else {
          resolve(response);
        }
      }).catch((error) => {
        log('unknown fetch error', error);
        reject(error);
      });
    }));
  };
}

Obviously that code is snipped from a larger project, but it might help someone else.

@robincafolla
Copy link

I should add that to get this library working on the front-end, if you're using webpack >= v5, you need to shim the require('url') call here.

Webpack removed it's nodejs polyfills in v5 so you need to install the native-url library and specify a resolve in your webpack.config.js:

module.exports = {
  // ...
  resolve: {
    fallback: {
      "url": require.resolve("native-url")
    }
  }
};

If this project is planning true front-end support (and it would be great to have it) it would make sense to use the WHATWG url api instead.

@AlexZeitler
Copy link
Contributor

@robincafolla thanks for showing your use case.

Right now I'm experimenting with parsing and trying to understand what is required.

So given this HTTP 400 problem detail repsonse,

{
  "type": "https://example.net/validation-error",
  "status": 400,
  "title": "Your request parameters didn't validate.",
  "instance": "https://example.net/account/logs/123",
  "invalid-params": [
    {
      "name": "age",
      "reason": "must be a positive integer"
    },
    {
      "name": "color",
      "reason": "must be 'green', 'red' or 'blue'"
    }
  ]
}

you could do this:

const problemDocument = fromJSON(status400JSON)

This would give you the typed representation of the problem according to RFC7807 but without the extension invalid-params.

As the type is not about:blank but a specific problem document type https://example.net/validation-error, the API docs for this particular problem can provide the schema for the extension.

On the client side we could then map the extension invalid-params as well.

I'm thinking of something like this (it's in the PR linked in my previous comment):

const mappers: HttpProblemExtensionMapper[] = [
  {
    type: 'https://example.net/validation-error',
    map: (object: any) =>
      new ProblemDocumentExtension({
        'invalid-params': object['invalid-params']
      })
  }
]

const problemDocument = fromJSON(status400JSON, mappers)

This would give us the document as shown above but including the extension.

So the idea is to have mappers for known extension schemas.

I'm also thinking about if it could make sense to have types for the specific problem documents like this:

type ValidationProblemDocument = ProblemDocument & {
  type: 'https://example.net/validation-error'
  'invalid-params': {
    name: string
    reason: string
  }[]
}

No we could have type guards to make evaluating the documents a bit more type safe in our code:

function isValidationProblemDocument(
  value: unknown
): value is ValidationProblemDocument {
  const x = value as ProblemDocument
  return x.type === 'https://example.net/validation-error'
}

if (isValidationProblemDocument(document)) {
  document['invalid-params'].length.should.equal(2)
}

That way we can even get IntelliSense for invalid-params within the if statement.

But I'm also thinking about if it's a good idea to go that far. I wonder if it wouldn't be better to have our own error types living in the client and one could just map the HTTP problems to our client errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants