diff --git a/cmd/serve.go b/cmd/serve.go index e2ca14d0..b5f2930c 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,7 +1,6 @@ package cmd import ( - "net/http" "strings" "github.com/cheqd/did-resolver/services" @@ -85,15 +84,12 @@ func serve() { } log.Debug().Msgf("Requested content type: %s", requestedContentType) - responseBody, err := requestService.ProcessDIDRequest(didUrl, types.ResolutionOption{Accept: requestedContentType}) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } + responseBody, status := requestService.ProcessDIDRequest(didUrl, types.ResolutionOption{Accept: requestedContentType}) log.Debug().Msgf("Response body: %s", responseBody) c.Response().Header().Set(echo.HeaderContentType, string(requestedContentType)) - return c.JSONBlob(http.StatusOK, []byte(responseBody)) + return c.JSONBlob(status, []byte(responseBody)) }) log.Info().Msg("Starting listener") diff --git a/config.yaml b/config.yaml index 6b1063ce..e90ea605 100644 --- a/config.yaml +++ b/config.yaml @@ -10,4 +10,4 @@ api: listener: "0.0.0.0:1313" resolverPath: "/1.0/identifiers/:did" -logLevel: "warn" +logLevel: "debug" diff --git a/services/request_service.go b/services/request_service.go index fd1025ef..beb753a6 100644 --- a/services/request_service.go +++ b/services/request_service.go @@ -29,108 +29,101 @@ func NewRequestService(didMethod string, ledgerService LedgerServiceI) RequestSe } } -func (rs RequestService) IsDidUrl(didUrl string) bool { - _, path, query, fragmentId, err := cheqdUtils.TrySplitDIDUrl(didUrl) - return err == nil && (path != "" || query != "" || fragmentId != "") -} - -func (rs RequestService) ProcessDIDRequest(didUrl string, resolutionOptions types.ResolutionOption) (string, error) { +func (rs RequestService) ProcessDIDRequest(didUrl string, resolutionOptions types.ResolutionOption) (string, int) { var result string - var err error - if rs.IsDidUrl(didUrl) { + var statusCode int + if utils.IsDidUrl(didUrl) { log.Trace().Msgf("Dereferencing %s", didUrl) - result, err = rs.prepareDereferencingResult(didUrl, types.DereferencingOption(resolutionOptions)) + result, statusCode = rs.prepareDereferencingResult(didUrl, types.DereferencingOption(resolutionOptions)) } else { log.Trace().Msgf("Resolving %s", didUrl) - result, err = rs.prepareResolutionResult(didUrl, resolutionOptions) + result, statusCode = rs.prepareResolutionResult(didUrl, resolutionOptions) } - return result, err + return result, statusCode } -func (rs RequestService) prepareResolutionResult(did string, resolutionOptions types.ResolutionOption) (string, error) { - didResolution, err := rs.Resolve(did, resolutionOptions) - if err != nil { - return "", err - } - - resolutionMetadata, err := json.Marshal(didResolution.ResolutionMetadata) - if err != nil { - return "", err - } - - didDoc, err := rs.didDocService.MarshallDID(didResolution.Did) - if err != nil { - return "", err - } +func (rs RequestService) prepareResolutionResult(did string, resolutionOptions types.ResolutionOption) (string, int) { + didResolution := rs.Resolve(did, resolutionOptions) - metadata, err := json.Marshal(&didResolution.Metadata) - if err != nil { - return "", err + resolutionMetadata, mErr1 := json.Marshal(didResolution.ResolutionMetadata) + didDoc, mErr2 := rs.didDocService.MarshallDID(didResolution.Did) + metadata, mErr3 := json.Marshal(&didResolution.Metadata) + if mErr1 != nil || mErr2 != nil || mErr3 != nil { + log.Error().Errs("errors", []error{mErr1, mErr2, mErr3}).Msg("Marshalling error") + return createJsonResolutionInternalError(resolutionMetadata) } if didResolution.ResolutionMetadata.ResolutionError != "" { didDoc, metadata = "", []byte{} } - return createJsonResolution(didDoc, string(metadata), string(resolutionMetadata)) + result, err := createJsonResolution(didDoc, string(metadata), string(resolutionMetadata)) + if err != nil { + log.Error().Err(err).Msg("Marshalling error") + return createJsonResolutionInternalError([]byte{}) + } + return result, didResolution.ResolutionMetadata.ResolutionError.GetStatusCode() } -func (rs RequestService) prepareDereferencingResult(did string, dereferencingOptions types.DereferencingOption) (string, error) { +func (rs RequestService) prepareDereferencingResult(did string, dereferencingOptions types.DereferencingOption) (string, int) { log.Info().Msgf("Dereferencing %s", did) - didDereferencing, err := rs.Dereference(did, dereferencingOptions) - if err != nil { - return "", err - } + didDereferencing := rs.Dereference(did, dereferencingOptions) - dereferencingMetadata, err := json.Marshal(didDereferencing.DereferencingMetadata) - if err != nil { - return "", err + dereferencingMetadata, mErr1 := json.Marshal(didDereferencing.DereferencingMetadata) + metadata, mErr2 := json.Marshal(didDereferencing.Metadata) + if mErr1 != nil || mErr2 != nil { + log.Error().Errs("errors", []error{mErr1, mErr2}).Msg("Marshalling error") + return createJsonDereferencingInternalError([]byte{}) } if didDereferencing.DereferencingMetadata.ResolutionError != "" { - return createJsonDereferencing(nil, "", string(dereferencingMetadata)) + didDereferencing.ContentStream = nil + metadata = []byte{} } - metadata, err := json.Marshal(didDereferencing.Metadata) + result, err := createJsonDereferencing(didDereferencing.ContentStream, string(metadata), string(dereferencingMetadata)) if err != nil { - return "", err + log.Error().Err(err).Msg("Marshalling error") + return createJsonDereferencingInternalError(dereferencingMetadata) } - return createJsonDereferencing(didDereferencing.ContentStream, string(metadata), string(dereferencingMetadata)) + return result, didDereferencing.DereferencingMetadata.ResolutionError.GetStatusCode() } // https://w3c-ccg.github.io/did-resolution/#resolving -func (rs RequestService) Resolve(did string, resolutionOptions types.ResolutionOption) (types.DidResolution, error) { +func (rs RequestService) Resolve(did string, resolutionOptions types.ResolutionOption) types.DidResolution { if !resolutionOptions.Accept.IsSupported() { - return types.DidResolution{ResolutionMetadata: types.NewResolutionMetadata(did, types.JSON, types.RepresentationNotSupportedError)}, nil + return types.DidResolution{ResolutionMetadata: types.NewResolutionMetadata(did, types.JSON, types.RepresentationNotSupportedError)} } didResolutionMetadata := types.NewResolutionMetadata(did, resolutionOptions.Accept, "") if didMethod, _, _, _ := cheqdUtils.TrySplitDID(did); didMethod != rs.didMethod { didResolutionMetadata.ResolutionError = types.MethodNotSupportedError - return types.DidResolution{ResolutionMetadata: didResolutionMetadata}, nil + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } if !cheqdUtils.IsValidDID(did, "", rs.ledgerService.GetNamespaces()) { didResolutionMetadata.ResolutionError = types.InvalidDIDError - return types.DidResolution{ResolutionMetadata: didResolutionMetadata}, nil + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } didDoc, metadata, isFound, err := rs.ledgerService.QueryDIDDoc(did) if err != nil { - return types.DidResolution{}, err + didResolutionMetadata.ResolutionError = types.InternalError + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } resolvedMetadata, err := rs.ResolveMetadata(did, metadata) if err != nil { - return types.DidResolution{}, err + didResolutionMetadata.ResolutionError = types.InternalError + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } if !isFound { didResolutionMetadata.ResolutionError = types.NotFoundError - return types.DidResolution{ResolutionMetadata: didResolutionMetadata}, nil + return types.DidResolution{ResolutionMetadata: didResolutionMetadata} } if didResolutionMetadata.ContentType == types.DIDJSONLD || didResolutionMetadata.ContentType == types.JSONLD { @@ -138,27 +131,27 @@ func (rs RequestService) Resolve(did string, resolutionOptions types.ResolutionO } else { didDoc.Context = []string{} } - return types.DidResolution{Did: didDoc, Metadata: resolvedMetadata, ResolutionMetadata: didResolutionMetadata}, nil + return types.DidResolution{Did: didDoc, Metadata: resolvedMetadata, ResolutionMetadata: didResolutionMetadata} } // https://w3c-ccg.github.io/did-resolution/#dereferencing -func (rs RequestService) Dereference(didUrl string, dereferenceOptions types.DereferencingOption) (types.DidDereferencing, error) { +func (rs RequestService) Dereference(didUrl string, dereferenceOptions types.DereferencingOption) types.DidDereferencing { did, path, query, fragmentId, err := cheqdUtils.TrySplitDIDUrl(didUrl) log.Info().Msgf("did: %s, path: %s, query: %s, fragmentId: %s", did, path, query, fragmentId) if !dereferenceOptions.Accept.IsSupported() { - return types.DidDereferencing{DereferencingMetadata: types.NewDereferencingMetadata(did, types.JSON, types.RepresentationNotSupportedError)}, nil + return types.DidDereferencing{DereferencingMetadata: types.NewDereferencingMetadata(did, types.JSON, types.RepresentationNotSupportedError)} } if err != nil || !cheqdUtils.IsValidDIDUrl(didUrl, "", []string{}) { dereferencingMetadata := types.NewDereferencingMetadata(didUrl, dereferenceOptions.Accept, types.InvalidDIDUrlError) - return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata}, nil + return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata} } // TODO: implement if query != "" { dereferencingMetadata := types.NewDereferencingMetadata(didUrl, dereferenceOptions.Accept, types.RepresentationNotSupportedError) - return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata}, nil + return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata} } var didDereferencing types.DidDereferencing @@ -169,10 +162,11 @@ func (rs RequestService) Dereference(didUrl string, dereferenceOptions types.Der } if err != nil { - return types.DidDereferencing{}, err + dereferencingMetadata := types.NewDereferencingMetadata(didUrl, dereferenceOptions.Accept, types.InternalError) + return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata} } - return didDereferencing, nil + return didDereferencing } func (rs RequestService) dereferencePrimary(path string, did string, didUrl string, dereferenceOptions types.DereferencingOption) (types.DidDereferencing, error) { @@ -202,16 +196,15 @@ func (rs RequestService) dereferencePrimary(path string, did string, didUrl stri } func (rs RequestService) dereferenceSecondary(did string, fragmentId string, didUrl string, dereferenceOptions types.DereferencingOption) (types.DidDereferencing, error) { - didResolution, err := rs.Resolve(did, types.ResolutionOption(dereferenceOptions)) - if err != nil { - return types.DidDereferencing{}, err - } - metadata := didResolution.Metadata + didResolution := rs.Resolve(did, types.ResolutionOption(dereferenceOptions)) + dereferencingMetadata := types.DereferencingMetadata(didResolution.ResolutionMetadata) if dereferencingMetadata.ResolutionError != "" { return types.DidDereferencing{DereferencingMetadata: dereferencingMetadata}, nil } + metadata := didResolution.Metadata + var protoContent protoiface.MessageV1 if fragmentId != "" { protoContent = rs.didDocService.GetDIDFragment(fragmentId, didResolution.Did) @@ -300,3 +293,19 @@ func createJsonDereferencing(contentStream json.RawMessage, metadata string, der return string(respJson), nil } + +func createJsonDereferencingInternalError(dereferencingMetadata []byte) (string, int) { + result, mErr := createJsonDereferencing(nil, "", string(dereferencingMetadata)) + if mErr != nil { + return "", types.InternalError.GetStatusCode() + } + return result, types.InternalError.GetStatusCode() +} + +func createJsonResolutionInternalError(resolutionMetadata []byte) (string, int) { + result, mErr := createJsonResolution("", "", string(resolutionMetadata)) + if mErr != nil { + return "", types.InternalError.GetStatusCode() + } + return result, types.InternalError.GetStatusCode() +} diff --git a/services/request_service_test.go b/services/request_service_test.go index 82a73ad9..1f1aeac1 100644 --- a/services/request_service_test.go +++ b/services/request_service_test.go @@ -217,7 +217,7 @@ func TestResolve(t *testing.T) { if expectedContentType == "" { expectedContentType = subtest.resolutionType } - resolutionResult, err := requestService.Resolve(id, types.ResolutionOption{Accept: subtest.resolutionType}) + resolutionResult := requestService.Resolve(id, types.ResolutionOption{Accept: subtest.resolutionType}) fmt.Println(subtest.name + ": resolutionResult:") fmt.Println(resolutionResult.Did.VerificationMethod) @@ -227,7 +227,6 @@ func TestResolve(t *testing.T) { require.EqualValues(t, expectedContentType, resolutionResult.ResolutionMetadata.ContentType) require.EqualValues(t, subtest.expectedError, resolutionResult.ResolutionMetadata.ResolutionError) require.EqualValues(t, expectedDIDProperties, resolutionResult.ResolutionMetadata.DidProperties) - require.Empty(t, err) }) } } @@ -346,7 +345,7 @@ func TestDereferencing(t *testing.T) { fmt.Println(" dereferencingResult " + subtest.didUrl) - dereferencingResult, err := requestService.Dereference(subtest.didUrl, types.DereferencingOption{Accept: subtest.dereferencingType}) + dereferencingResult := requestService.Dereference(subtest.didUrl, types.DereferencingOption{Accept: subtest.dereferencingType}) fmt.Println(subtest.name + ": dereferencingResult:") fmt.Println(dereferencingResult) @@ -355,7 +354,6 @@ func TestDereferencing(t *testing.T) { require.EqualValues(t, subtest.dereferencingType, dereferencingResult.DereferencingMetadata.ContentType) require.EqualValues(t, subtest.expectedError, dereferencingResult.DereferencingMetadata.ResolutionError) require.EqualValues(t, expectedDIDProperties, dereferencingResult.DereferencingMetadata.DidProperties) - require.Empty(t, err) }) } } diff --git a/tests/pytest/test_resolution.py b/tests/pytest/test_resolution.py index 8973c697..76ae4847 100644 --- a/tests/pytest/test_resolution.py +++ b/tests/pytest/test_resolution.py @@ -41,28 +41,33 @@ def test_resolution(did_url, expected_output): @pytest.mark.parametrize( - "accept, expected_header, has_context, expected_body", + "accept, expected_header, has_context, expected_status_code, expected_body", [ - (LDJSON, LDJSON, True, r"(.*?)didResolutionMetadata(.*?)application/ld\+json" - r"(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), - (DIDLDJSON, DIDLDJSON, True, "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json" - "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), - ("*/*", DIDLDJSON, True, "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json" - "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), - (DIDJSON, DIDJSON, False, r"(.*?)didResolutionMetadata(.*?)application/did\+json" - r"(.*?)didDocument(.*?)(?!`@context`)(.*?)didDocumentMetadata"), - (HTML + ",application/xhtml+xml", JSON, False, + (LDJSON, LDJSON, True, 200, + r"(.*?)didResolutionMetadata(.*?)application/ld\+json" + r"(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), + (DIDLDJSON, DIDLDJSON, True, 200, + "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json" + "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), + ("*/*", DIDLDJSON, True, 200, + "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json" + "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"), + (DIDJSON, DIDJSON, False, 200, + r"(.*?)didResolutionMetadata(.*?)application/did\+json" + r"(.*?)didDocument(.*?)(?!`@context`)(.*?)didDocumentMetadata"), + (HTML + ",application/xhtml+xml", JSON, False, 406, "(.*?)didResolutionMetadata(.*?)\"error\":\"representationNotSupported\"" "(.*?)\"didDocument\":null,\"didDocumentMetadata\":\[\]"), ] ) -def test_resolution_content_type(accept, expected_header, expected_body, has_context): +def test_resolution_content_type(accept, expected_header, expected_body, has_context, expected_status_code): url = RESOLVER_URL + PATH + TESTNET_DID header = {"Accept": accept} if accept else {} r = requests.get(url, headers=header) assert r.headers["Content-Type"] == expected_header + assert r.status_code == expected_status_code assert re.match(expected_body, r.text) if has_context: assert re.findall(r"context", r.text) @@ -71,35 +76,36 @@ def test_resolution_content_type(accept, expected_header, expected_body, has_con dereferencing_content_type_test_set = [ - (LDJSON, LDJSON, True, + (LDJSON, LDJSON, True, 200, r"(.*?)contentStream(.*?)@context(.*?)contentMetadata" r"(.*?)dereferencingMetadata(.*?)application/ld\+json"), - (DIDLDJSON, DIDLDJSON, True, + (DIDLDJSON, DIDLDJSON, True, 200, "(.*?)contentStream(.*?)@context(.*?)contentMetadata" "(.*?)dereferencingMetadata(.*?)application/did\+ld\+json"), - ("*/*", DIDLDJSON, True, + ("*/*", DIDLDJSON, True, 200, "(.*?)contentStream(.*?)@context(.*?)contentMetadata" "(.*?)dereferencingMetadata(.*?)application/did\+ld\+json"), - (DIDJSON, DIDJSON, False, + (DIDJSON, DIDJSON, False, 200, r"(.*?)contentStream(.*?)contentMetadata" r"(.*?)dereferencingMetadata(.*?)application/did\+json"), - (HTML + ",application/xhtml+xml", JSON, False, + (HTML + ",application/xhtml+xml", JSON, False, 406, "(.*?)\"contentStream\":null,\"contentMetadata\":\[\]," "\"dereferencingMetadata(.*?)\"error\":\"representationNotSupported\""), ] @pytest.mark.parametrize( - "accept, expected_header, has_context, expected_body", + "accept, expected_header, has_context, expected_status_code, expected_body", dereferencing_content_type_test_set ) -def test_dereferencing_content_type_fragment(accept, expected_header, expected_body, has_context): +def test_dereferencing_content_type_fragment(accept, expected_header, expected_body, has_context, expected_status_code): url = RESOLVER_URL + PATH + TESTNET_RESOURCE header = {"Accept": accept} if accept else {} r = requests.get(url, headers=header) assert r.headers["Content-Type"] == expected_header + assert r.status_code == expected_status_code assert re.match(expected_body, r.text) if has_context: assert re.findall(r"context", r.text) @@ -108,10 +114,10 @@ def test_dereferencing_content_type_fragment(accept, expected_header, expected_b @pytest.mark.parametrize( - "accept, expected_header, has_context, expected_body", + "accept, expected_header, has_context, expected_status_code, expected_body", dereferencing_content_type_test_set ) -def test_dereferencing_content_type_resource(accept, expected_header, expected_body, has_context): +def test_dereferencing_content_type_resource(accept, expected_header, expected_body, has_context, expected_status_code): url = RESOLVER_URL + PATH + TESTNET_FRAGMENT.replace("#", "%23") header = {"Accept": accept} if accept else {} @@ -123,3 +129,26 @@ def test_dereferencing_content_type_resource(accept, expected_header, expected_b assert re.findall(r"context", r.text) else: assert not re.findall(r"context", r.text) + + +@pytest.mark.parametrize( + "did_url, expected_status_code", + [ + (TESTNET_DID, 200), + (TESTNET_FRAGMENT, 200), + (TESTNET_RESOURCE, 200), + (FAKE_TESTNET_DID, 404), + (FAKE_TESTNET_FRAGMENT, 404), + (FAKE_TESTNET_RESOURCE, 404), + ("did:wrong_method:MTMxDQKMTMxDQKMT", 406), + (TESTNET_DID + "/", 406), + (TESTNET_DID + "invalidDID", 400), + ] +) +def test_resolution_status_code(did_url, expected_status_code): + url = RESOLVER_URL + PATH + did_url.replace("#", "%23") + r = requests.get(url) + + print(url) + print(r.text) + assert r.status_code == expected_status_code diff --git a/utils/did_url.go b/utils/did_url.go index f076ac14..fe3834e2 100644 --- a/utils/did_url.go +++ b/utils/did_url.go @@ -2,6 +2,8 @@ package utils import ( "regexp" + + cheqdUtils "github.com/cheqd/cheqd-node/x/cheqd/utils" ) var ( @@ -17,3 +19,8 @@ func GetResourceId(didUrlPath string) (id string) { match := ResourceId.FindStringSubmatch(didUrlPath) return match[0] } + +func IsDidUrl(didUrl string) bool { + _, path, query, fragmentId, err := cheqdUtils.TrySplitDIDUrl(didUrl) + return err == nil && (path != "" || query != "" || fragmentId != "") +}