Return Swagger JSON from backend service for custom route like /docs

Not applicable

As far as I know it's not possible out of the box to return the Swagger JSON in Apigee. Although that Swagger has been used to generate the proxy, it's not possible to return the contents of that file to the caller.

I'm looking for a way to return the Swagger JSON to the caller, so I created a proxy flow which is triggered by a GET for /docs pattern.

I thought it would (or could) be sufficient to use the AssignMessage policy to rewrite the URL to the backend service swagger, by using the 'AssignVariable' setting:

<AssignVariable>
  <Name>target.url</Name>
  <Value>https://some-service.com/docs/v1/swagger.json</Value>
  <Ref/>
</AssignVariable>

But this doesn't appear to work?

When I look at the trace window I see the target.url is populated with my url but it's not used for accessing my backend service?

Is there anything else I need to do to accomplish this?

Solved Solved
2 6 1,257
1 ACCEPTED SOLUTION

Not applicable

In the end I managed to solve it using this approach:

1) Add a 'Swagger' flow in the proxy flow

<Flow name="Swagger">
  <Request/>
  <Response/>
  <Condition>
    (proxy.pathsuffix MatchesPath "/api/v1.0/docs") and (request.verb = "GET")
  </Condition>
</Flow>

2) Add an Assign Message policy to set the target.url to the swagger location, containing this part

<AssignVariable>
  <Name>target.url</Name>
  <Value>https://some-backendservice.com/docs/v1/swagger.json</Value>
  <Ref/>
</AssignVariable>

3) Add a 'Swagger' flow in the target flow

<Flow name="Swagger">
  <Request>
    <Step>
      <Name>Set-Swagger-Endpoint</Name>
    </Step>
  </Request>
  <Condition>
    (proxy.pathsuffix MatchesPath "/api/v1.0/docs") and (request.verb = "GET")
  </Condition>
</Flow>

It would be great when I didn't need to specify the Swagger endpoint in the Assign Message policy, but could use the <spec>.....</spec> entry in the ApiProxy definition instead, as this also contains the endpoint.

View solution in original post

6 REPLIES 6

Where is your AssignMessage policy attached?

Setting target.url is possible "anywhere", but target.url gets reset at the start of the target flow. Therefore if you attach your policy in the proxy Request flow, ... it will "work" as you observed in the Trace output, but then the value gets reset later. If you attach the same policy in the target request flow, then it should do what you want, I think.

More on flows here.

aside from resetting the target.url, there are other ways to accomplish what you want.

  1. add a new target! Your proxy bundle can have more than one target. You can route to different targets based on the inbound verb / resource. You can learn about RouteRules here.
  2. Use AssignMessage to assign the response directly. For this you would "hard-code" the desired JSON into the AssignMessage. You'd couple this with a RouteRule that does not route to any target. It would look something like this:

      <RouteRule name='NoRouteRule'>
        <Condition>proxy.pathsuffix MatchesPath "/swagger"</Condition>
      </RouteRule>
    
      <RouteRule name='default'>
        <TargetEndpoint>default</TargetEndpoint>
      </RouteRule>
    

Thanks for the quick replies!

I found out that indeed the target.url only works when set in the target flow, so I was looking at putting this part in the target flow. However, I have a catch-all flow in the proxy flow which will go off when I would use the target flow only.

I've tried using the RouteRule and indeed it goes to my backend. The thing I still have to fix is the fact that the proxy suffix is appended to my target endpoint.

So I'm requesting http://some-service.com/v1/docs and this should go to http://some-backend-service.com/docs but it currently goes to http://some-backend-service/docs/v1/docs which obviously leads to a 404.

Also what about using route rules here where

<Condition>request.message.path MatchesPath "/docs/**</Condition><TargetEndpoint>DocTarget<TargetEndpoint>

http://docs.apigee.com/api-services/content/understanding-routes

Not applicable

In the end I managed to solve it using this approach:

1) Add a 'Swagger' flow in the proxy flow

<Flow name="Swagger">
  <Request/>
  <Response/>
  <Condition>
    (proxy.pathsuffix MatchesPath "/api/v1.0/docs") and (request.verb = "GET")
  </Condition>
</Flow>

2) Add an Assign Message policy to set the target.url to the swagger location, containing this part

<AssignVariable>
  <Name>target.url</Name>
  <Value>https://some-backendservice.com/docs/v1/swagger.json</Value>
  <Ref/>
</AssignVariable>

3) Add a 'Swagger' flow in the target flow

<Flow name="Swagger">
  <Request>
    <Step>
      <Name>Set-Swagger-Endpoint</Name>
    </Step>
  </Request>
  <Condition>
    (proxy.pathsuffix MatchesPath "/api/v1.0/docs") and (request.verb = "GET")
  </Condition>
</Flow>

It would be great when I didn't need to specify the Swagger endpoint in the Assign Message policy, but could use the <spec>.....</spec> entry in the ApiProxy definition instead, as this also contains the endpoint.

Thanks for describing your solution; this is helpful.

Also I agree with you that ideally you should not need to specify the swagger endpoint, and should be able to deliver a spec.

@Marsh Gardiner @Marsh Gardiner - what do you think ?

Not applicable

Something we are seeing customers do is include a JSC snip that includes their Open API Specification document such that it can be directly accessed, manipulated or returned by the MP.

That JSC would be saved in a file oasDoc.js and look something like this:

var oasDoc={
    "swagger": "2.0",
    "info": {
        "version": "1.0.0",
        "title": "Swagger Petstore",
        "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
        "termsOfService": "http://swagger.io/terms/",
        "contact": {
            "name": "Swagger API Team"
        },
        "license": {
            "name": "MIT"
        }
    },
    "host": "petstore.swagger.io",
    "basePath": "/api",
    "schemes": [
        "http"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/pets": {
            "get": {
                "description": "Returns all pets from the system that the user has access to",
                "produces": [
                    "application/json"
                ],
                "responses": {
                    "200": {
                        "description": "A list of pets.",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Pet"
                            }
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "Pet": {
            "type": "object",
            "required": [
                "id",
                "name"
            ],
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64"
                },
                "name": {
                    "type": "string"
                },
                "tag": {
                    "type": "string"
                }
            }
        }
    }
};

This bit of JSC can then be referenced in the Javascript policy as follows:

<Javascript name="jsGetOASDoc.js" timeLimit="200">
    <DisplayName>jsGetOASDoc</DisplayName>
    <ResourceURL>jsc://jsGetOASDoc.js</ResourceURL>
    <IncludeURL>jsc://oasDoc.js</IncludeURL>
</Javascript>

The Javascript to populate a flow variable with the string version of the doc is quite simple:

//jsGetOASDoc.js
//note that if we included the oasDoc.js file we have a variable defined that
//contains the contents

if(oasDoc){
  context.setVariable("oasDoc", JSON.stringify(oasDoc));
}

From here you can simply assign the flow variable "oasDoc" you the response.

Hope this helps!