This is Day 14 of Butter Days, from Achilles Coffee Roasters on Cortez Hill in Sandiego, CA.

This week I have slightly more time, but still only a couple hours because I’ll be on a train soon.

This week I’m going to try to go through the warnings that the openapi generator gives me about the AWS Swagger Definition one by one, and try to fix as many as I can.

See Day 1 of Butter Days for context on what I’m ultimately trying to build.



Testing Flow

Before I do anything, I want a quick way to test any changes I make to see if they made a difference. First, here’s a quick script to wrap the openapi-generator docker command that I’ve been using to generate the rust code:

#!/bin/bash

# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail

NUM_ARGS_REQUIRED=1
if [ $# -ne "${NUM_ARGS_REQUIRED}" ]; then
    cat <<EOF
Usage: $0 <openapi-directory>

    Try to generate IAM from openapi directory.

EOF
    exit 1
fi

run () {
    echo "+" "$@" 1>&2
    "$@"
}

OPENAPI_DIRECTORY=$1

pushd "${OPENAPI_DIRECTORY}/APIs/amazonaws.com/iam/2010-05-08"
docker run --rm -v "${PWD}:/local/out/rust/" openapitools/openapi-generator-cli generate \
    -i /local/out/rust/swagger.yaml \
    -g rust \
    -o /local/out/rust/generated \
    --additional-properties packageName=aws_iam_client --library=reqwest
popd

This combined with the cleanup of the update.sh generator script I did two weeks ago means I can run the whole conversion and generation process with this one liner:

 ./update.sh v2.561.0 && ./generate.sh openapi-directory

It takes a minute to run end to end, but that will give me some time to think. As long as the active effort and thought from me is small each time I want to test then I’m happy. One command to get feedback. That’s a good situation.

Unrecognized Twitter Field

From last time, I found that the author of the aws2openapi project had added their twitter account in info.contact.x-twitter, which caused the openapi-generator to fail.

I filed an issue for that and I’m just going to remove that field.

Unknown Base Type

The next warning is this one:

[main] WARN  o.o.codegen.DefaultCodegen - codegenModel is null. Default to UNKNOWN_BASE_TYPE

I don’t really understand what’s going on here, but from codegenModel is null I’m thinking that I should understand what codegenModel is.

Here’s the first google result for “codegenmodel is null”. From looking at the full set of warnings they posted and where they match mine, it looks like another warning that I’m seeing is actually related to the first one:

[main] WARN  o.o.codegen.DefaultCodegen - The following schema has undefined (null) baseType. It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. A correct 'consumes' for form parameters should be 'application/x-www-form-urlencoded' or 'multipart/?'
[main] WARN  o.o.codegen.DefaultCodegen - schema: class Schema {
    type: null
    format: null
    $ref: null
    description: null
    title: null
    multipleOf: null
    maximum: null
    exclusiveMaximum: null
    minimum: null
    exclusiveMinimum: null
    maxLength: null
    minLength: null
    pattern: null
    maxItems: null
    minItems: null
    uniqueItems: null
    maxProperties: null
    minProperties: null
    required: [CertificateBody]
    not: null
    properties: {UserName=class StringSchema {
        class Schema {
            type: string
            format: null
            $ref: null
            description: <p>The name of the user the signing certificate is for.</p> <p>This parameter allows (through its <a href="http://wikipedia.org/wiki/regex">regex pattern</a>) a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: _+=,.@-</p>
            title: null
            multipleOf: null
            maximum: null
            exclusiveMaximum: null
            minimum: null
            exclusiveMinimum: null
            maxLength: 128
            minLength: 1
            pattern: [\w+=,.@-]+
            maxItems: null
            minItems: null
            uniqueItems: null
            maxProperties: null
            minProperties: null
            required: null
            not: null
            properties: null
            additionalProperties: null
            nullable: null
            readOnly: null
            writeOnly: null
            example: null
            externalDocs: null
            deprecated: null
            discriminator: null
            xml: null
        }
    }, CertificateBody=class StringSchema {
        class Schema {
            type: string
            format: null
            $ref: null
            description: <p>The contents of the signing certificate.</p> <p>The <a href="http://wikipedia.org/wiki/regex">regex pattern</a> used to validate this parameter is a string of characters consisting of the following:</p> <ul> <li> <p>Any printable ASCII character ranging from the space character (\u0020) through the end of the ASCII character range</p> </li> <li> <p>The printable characters in the Basic Latin and Latin-1 Supplement character set (through \u00FF)</p> </li> <li> <p>The special characters tab (\u0009), line feed (\u000A), and carriage return (\u000D)</p> </li> </ul>
            title: null
            multipleOf: null
            maximum: null
            exclusiveMaximum: null
            minimum: null
            exclusiveMinimum: null
            maxLength: 16384
            minLength: 1
            pattern: [\u0009\u000A\u000D\u0020-\u00FF]+
            maxItems: null
            minItems: null
            uniqueItems: null
            maxProperties: null
            minProperties: null
            required: null
            not: null
            properties: null
            additionalProperties: null
            nullable: null
            readOnly: null
            writeOnly: null
            example: null
            externalDocs: null
            deprecated: null
            discriminator: null
            xml: null
        }
    }}
    additionalProperties: null
    nullable: null
    readOnly: null
    writeOnly: null
    example: null
    externalDocs: null
    deprecated: null
    discriminator: null
    xml: null
}

This is starting to clear things up a little bit. The warning even gives me a suggestion: It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. A correct 'consumes' for form parameters should be 'application/x-www-form-urlencoded' or 'multipart/?'. Where is this “consumes” field? Let’s check the spec.

Well, the only time I see “consumes” in the spec is here:

consumes:
  - text/xml

That looks like the issue. From this blog post about what’s new in openapi 3.0, it looks like there were some changes related to how content types are specified. Many of those bullet points say things that seem relevant, but one of them says “Operations can now consume both formdata and other media types such as JSON.”. This seems like the right path to follow.

Sure enough, there’s a requestBody keyword in openapi 3.0 that allows specifying content differently, and their example even has application/xml as a content type.

Because running a test is so easy now, I’m going to try adding application/xml instead of text/xml just to see what happens. Nope. Still got the same error, so it wasn’t that easy.

Hello XML My Old Friend

A few weeks ago, I found this post about generically parsing XML. Basically that post is arguing that parsing XML into a generic map isn’t really possible like it is with something like JSON, at minimum because XML can have duplicate keys. I wouldn’t be suprised if it also has other things that make it harder to parse. It’s XML.

Actually, now that I’m reading the full thread again, it looks like someone posted a link to a project that can parse XML. I somehow missed that before. Maybe it’s more hopeful than I thought.

The reason I’m looking into this is because I’m suspicious that the issue isn’t that the swagger 2.0 spec doesn’t support XML but rather that openapi-generator codegen doesn’t support XML. If it was just the spec maybe I could convert the spec to openapi 3.0, but if it’s the code generator I’ll have to add support for generating code that can parse XML.

Let’s try to find out. First, I found what looks like the directory with the rust code generators. The first, very cursory search doesn’t look good:

 $ git grep json | wc -l
23
 $ git grep xml | wc -l
0

23 lines involving json and no mention of XML. I think this tells me that the generator itself needs to have support for XML and it does not.

Yikes, this is not where I want to be. I didn’t sign up for writing XML parsing logic. Let’s see if I can get out of this work.

Let’s try the java module. Java is “enterprise grade”, so maybe that generator supports XML.

 $ git grep json | wc -l
129
 $ git grep xml | wc -l
271

All right, that looks a little more promising. Maybe I won’t have to write this myself after all. Getting XML support in the rust generator might be a nice thing to upstream at some point, but for now I just want to figure out what it takes to get all this working.

Just to be crazy, let’s try running the java generator and see what happens:

 $ docker run --rm openapitools/openapi-generator-cli list | grep java
    - java
    - javascript
    - javascript-closure-angular
    - javascript-flowtyped
    - java-inflector
    - java-msf4j
    - java-pkmst
    - java-play-framework
    - java-undertow-server
    - java-vertx

Now in my generate.sh script I’ll just replace all instances of rust with java.

docker run --rm -v "${PWD}:/local/out/java/" openapitools/openapi-generator-cli generate \
    -i /local/out/java/swagger.yaml \
    -g java \
    -o /local/out/java/generated \
    --additional-properties packageName=aws_iam_client

Hmm, well I actually get the same errors back out. That’s good to know.

[main] WARN  o.o.codegen.DefaultCodegen - codegenModel is null. Default to UNKNOWN_BASE_TYPE
[main] WARN  o.o.codegen.DefaultCodegen - The following schema has undefined (null) baseType. It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. A correct 'consumes' for form parameters should be 'application/x-www-form-urlencoded' or 'multipart/?'

I have a feeling that I’m actually going to have to deal with both issues. I suspect that the swagger 2.0 spec might not really support this content type and that the rust code generator doesn’t support XML. So that means to get this working I’ll have to both convert the spec to openapi 3.0 and implement XML parsing in the generated rust code.

Well, that’s too bad, but at least I know exactly what I need to do now.

Next Time

Before today I didn’t have a good sense of how much would actually be needed to get this working, or if it was even possible. Now that the issues are a little more narrowed down I think I can see the way to get where I want to go.

Next time, I want to run the swagger 2.0 to openapi 3.0 converter on the swagger 2.0 specs I’ve been using, and try to run the openapi-generator on the resulting openapi 3.0 spec to see what errors come out.

For now I’ll just generate the java client, knowing that if I want a rust client I’ll need to add XML support. Probably not an insane amount of work, but it is a distraction from what I’m actually trying to do.

I’m also curious whether the openapi-directory already has any specs in the openapi 3.0 format, and if so where they put them. Then when I generate and get the openapi 3.0 specs working I can possibly commit them back to the directory.