haproxytech / dataplaneapi

HAProxy Data Plane API
https://www.haproxy.com/documentation/dataplaneapi/
Apache License 2.0
326 stars 76 forks source link

OpenAPI spec creates Golang sources incompatible with examples #62

Closed akutz closed 4 years ago

akutz commented 4 years ago

The examples at https://www.haproxy.com/documentation/hapee/1-9r1/configuration/dataplaneapi/ are incompatible with the Golang client bindings generated from the OpenAPI spec. Aside from having to create a program to remove the duplicate Opt structs, I've also had to do yet some more post-processing to make the Backend model compatible with the aforementioned examples:

.PHONY: fix-backend-model
fix-backend-model: | $(GOLANGCI_LINT)
fix-backend-model: $(OUTPUT_DIR)/model_backend.go
fix-backend-model: ## Makes optional fields in the backend model into pointers
    sed -i.bak \
      -e 's~Forwardfor[[:space:]]\{1,\}Forwardfor~Forwardfor *Forwardfor~' \
      -e 's~HashType[[:space:]]\{1,\}BackendHashType~HashType *BackendHashType~' \
      -e 's~StickTable[[:space:]]\{1,\}BackendStickTable~StickTable *BackendStickTable~' \
      $<
    $(GOLANGCI_LINT) run -v --no-config --fast=false --fix --disable-all --enable goimports $<
    @rm -f $<.bak

If those fields aren't pointers, then you end up with obtuse errors (when using transactions) as a result of trying to create a new backend. While the values of Forwardfor.Enabled, HashType.Method, BackendStickTable.Type, and BackendStickTable.Size may all be tagged as omitempty, the server disagrees when empty values are submitted for the struct (and thus its fields).

I had to do the following to get a backend successfully created in Golang, and I'm not even sure if those values are the defaults for those fields.

    // Create a backend.
    backend, _, err := client.BackendApi.CreateBackend(ctx, hapi.Backend{
        Name: "lb-backend",
        Mode: "tcp",
        Balance: hapi.Balance{
            Algorithm: "roundrobin",
        },
        Forwardfor: hapi.Forwardfor{
            Enabled: "enabled",
        },
        HashType: hapi.BackendHashType{
            Method: "consistent",
        },
        Redispatch: hapi.Redispatch{
            Enabled: "disabled",
        },
        AdvCheck: "tcp-check",
        StickTable: hapi.BackendStickTable{
            Type: "ip2",
            Size: ptrInt32(100),
        },
    }, &hapi.CreateBackendOpts{
        TransactionId: transactionID,
    })
    if err != nil {
        t.Fatalf("failed to create backend: %v", err)
    }
akutz commented 4 years ago

FWIW, I've got a working Golang example of creating and testing an LB with the dataplane API. It starts at https://github.com/akutz/cluster-api-provider-vsphere/blob/feature/haproxy/pkg/haproxy/haproxy_test.go.

mjuraga commented 4 years ago

Hi, try generating a client using this: https://github.com/go-swagger/go-swagger

We use it to generate both the API and the github.com/haproxytech/models package. With the client lib there you can use the already existing models.

akutz commented 4 years ago

Hi @mjuraga,

Thank you for this. I didn't see the models package, nice. However, in trying to use go-swagger, it's complaining that the OpenAPI spec isn't valid. Is there some other spec file I should be using?

akutz commented 4 years ago

Hi @mjuraga,

Nevermind, I found it :) https://github.com/haproxytech/dataplaneapi-specification

akutz commented 4 years ago

Hi @mjuraga,

Whether I use Go-Swagger's latest release (v0.21.0) or v0.19.0 (as indicated at https://github.com/haproxytech/models), I receive the following errors with the spec:

$ docker run --rm -it \
>   -v "$(pwd)":/build quay.io/goswagger/swagger:v0.21.0 \
>   validate \
>   https://raw.githubusercontent.com/haproxytech/dataplaneapi-specification/v1.2.4/haproxy-spec.yaml
2020/01/04 15:37:48 
The swagger spec at "https://raw.githubusercontent.com/haproxytech/dataplaneapi-specification/v1.2.4/haproxy-spec.yaml" showed up some valid but possibly unwanted constructs.
2020/01/04 15:37:48 See warnings below:
2020/01/04 15:37:48 - WARNING: parameter "#/parameters/force_reload" is not used anywhere
2020/01/04 15:37:48 - WARNING: parameter "#/parameters/transaction_id" is not used anywhere
2020/01/04 15:37:48 - WARNING: parameter "#/parameters/version" is not used anywhere
2020/01/04 15:37:48 - WARNING: response "#/responses/BadRequest" is not used anywhere
2020/01/04 15:37:48 - WARNING: response "#/responses/NotFound" is not used anywhere
2020/01/04 15:37:48 - WARNING: response "#/responses/AlreadyExists" is not used anywhere
2020/01/04 15:37:48 - WARNING: response "#/responses/DefaultError" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/acls" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/filters" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/default_server" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/http_request_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/backend_switching_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/tcp_response_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/frontends" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/sites" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/reloads" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/process_info" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/native_stat" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/redispatch" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/defaults" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/tcp_request_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/global" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/http_response_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/backends" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/stick_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/native_stats" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/httpchk" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/balance" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/errorfile" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/forwardfor" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/endpoints" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/server_switching_rules" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/servers" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/log_targets" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/transactions" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/binds" is not used anywhere
2020/01/04 15:37:48 - WARNING: definition "#/definitions/info" is not used anywhere

The swagger spec at "https://raw.githubusercontent.com/haproxytech/dataplaneapi-specification/v1.2.4/haproxy-spec.yaml" is invalid against swagger specification 2.0.
See errors below:
- invalid ref "paths/haproxy.yaml#/filters"
- invalid ref "models/general.yaml#/process_info"
- invalid ref "models/haproxy.yaml#/bind"
- invalid ref "paths/haproxy.yaml#/defaults"
- invalid ref "paths/haproxy.yaml#/tcp_response_rules"
- invalid ref "models/general.yaml#/transaction"
- invalid ref "models/stats.yaml#/native"
- invalid ref "models/haproxy.yaml#/acl"
- invalid ref "models/haproxy.yaml#/global"
- invalid ref "models/haproxy.yaml#/httpchk"
- invalid ref "paths/haproxy.yaml#/frontends_one"
- invalid ref "paths/general.yaml#/specification"
- invalid ref "paths/general.yaml#/info"
- invalid ref "paths/simple.yaml#/sites_one"
- invalid ref "paths/haproxy.yaml#/backend_switching_rules"
- invalid ref "paths/general.yaml#/stats"
- invalid ref "paths/haproxy.yaml#/stick_rules"
- invalid ref "paths/stats.yaml#/native"
- invalid ref "paths/general.yaml#/configuration"
- invalid ref "paths/general.yaml#/reloads_one"
- invalid ref "models/haproxy.yaml#/filter"
- invalid ref "paths/general.yaml#/haproxy"
- invalid ref "paths/haproxy.yaml#/backends"
- invalid ref "models/general.yaml#/info"
- invalid ref "models/general.yaml#/reload"
- invalid ref "paths/haproxy.yaml#/http_request_rules_one"
- invalid ref "models/haproxy.yaml#/server_switching_rule"
- invalid ref "models/simple.yaml#/site"
- invalid ref "models/stats.yaml#/native_stats"
- invalid ref "paths/haproxy.yaml#/acls"
- invalid ref "paths/haproxy.yaml#/server_switching_rules"
- invalid ref "paths/general.yaml#/root"
- invalid ref "paths/haproxy.yaml#/stick_rules_one"
- invalid ref "paths/haproxy.yaml#/tcp_request_rules_one"
- invalid ref "paths/general.yaml#/transactions_one"
- invalid ref "models/haproxy.yaml#/backend_switching_rule"
- invalid ref "models/haproxy.yaml#/tcp_response_rule"
- invalid ref "paths/haproxy.yaml#/tcp_request_rules"
- invalid ref "paths/haproxy.yaml#/log_targets_one"
- invalid ref "paths/haproxy.yaml#/log_targets"
- invalid ref "models/haproxy.yaml#/http_response_rule"
- invalid ref "models/haproxy.yaml#/log_target"
- invalid ref "models/errors.yaml#/error"
- invalid ref "models/haproxy.yaml#/default_server"
- invalid ref "paths/haproxy.yaml#/tcp_response_rules_one"
- invalid ref "paths/haproxy.yaml#/frontends"
- invalid ref "paths/general.yaml#/reloads"
- invalid ref "paths/haproxy.yaml#/http_response_rules"
- invalid ref "models/haproxy.yaml#/http_request_rule"
- invalid ref "models/haproxy.yaml#/stick_rule"
- invalid ref "paths/general.yaml#/transactions"
- invalid ref "paths/haproxy.yaml#/server_switching_rules_one"
- invalid ref "paths/haproxy.yaml#/backend_switching_rules_one"
- invalid ref "models/haproxy.yaml#/frontend"
- invalid ref "paths/haproxy.yaml#/acls_one"
- invalid ref "paths/haproxy.yaml#/backends_one"
- invalid ref "paths/haproxy.yaml#/servers_one"
- invalid ref "models/haproxy.yaml#/balance"
- invalid ref "paths/haproxy.yaml#/filters_one"
- invalid ref "models/haproxy.yaml#/server"
- invalid ref "paths/haproxy.yaml#/binds"
- invalid ref "paths/haproxy.yaml#/http_response_rules_one"
- invalid ref "paths/general.yaml#/services"
- invalid ref "models/haproxy.yaml#/backend"
- invalid ref "models/haproxy.yaml#/errorfile"
- invalid ref "paths/haproxy.yaml#/servers"
- invalid ref "paths/haproxy.yaml#/global"
- invalid ref "models/haproxy.yaml#/tcp_request_rule"
- invalid ref "models/haproxy.yaml#/defaults"
- invalid ref "paths/haproxy.yaml#/configuration"
- invalid ref "paths/general.yaml#/process_info"
- invalid ref "paths/haproxy.yaml#/binds_one"
- invalid ref "models/haproxy.yaml#/redispatch"
- invalid ref "paths/simple.yaml#/sites"
- invalid ref "models/general.yaml#/endpoint"
- invalid ref "paths/haproxy.yaml#/http_request_rules"
- invalid ref "models/haproxy.yaml#/forwardfor"

The same errors occur with Go-Swagger v0.19.0.

akutz commented 4 years ago

Now this is interesting. Downloading the spec first and then running the validate command works (with warnings):

$ docker run --rm -it   -v "/Users/akutz/Downloads":/build quay.io/goswagger/swagger:v0.21.0   validate   /build/haproxy_spec.yaml
2020/01/04 15:41:52 
The swagger spec at "/build/haproxy_spec.yaml" is valid against swagger specification 2.0
2020/01/04 15:41:52 
The swagger spec at "/build/haproxy_spec.yaml" showed up some valid but possibly unwanted constructs.
2020/01/04 15:41:52 See warnings below:
2020/01/04 15:41:52 - WARNING: example value for data in body does not validate its schema
2020/01/04 15:41:52 - WARNING: data.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: in operation "createTCPRequestRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: in operation "createTCPRequestRule", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: data.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: in operation "createTCPResponseRule", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: in operation "createTCPResponseRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: data.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: in operation "createStickRule", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: in operation "createStickRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: data.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: data.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: data.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: in operation "createBackend", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: 201.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: 201.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: in operation "createBackend", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: 202.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: 202.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: data.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "createServer", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "createServer", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: data.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "createSite", example value in response 201 does not validate its schema
2020/01/04 15:41:52 - WARNING: 201.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "createSite", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: 202.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "getServer", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "getTCPRequestRules", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: in operation "getSites", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "getServers", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "getStickRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: in operation "getBackends", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: in operation "getTCPResponseRules", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: in operation "getTCPResponseRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: in operation "getTCPRequestRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: in operation "getStickRules", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.items.example.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: in operation "getBackend", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: 200.data.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: 200.data.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: in operation "getSite", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.data.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "replaceStickRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.example.type in body should be one of [match on store-request store-response]
2020/01/04 15:41:52 - WARNING: in operation "replaceStickRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceTCPResponseRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: in operation "replaceTCPResponseRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceBackend", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: 200.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: 200.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: in operation "replaceBackend", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceTCPRequestRule", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: in operation "replaceTCPRequestRule", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceServer", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: in operation "replaceServer", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceSite", example value in response 202 does not validate its schema
2020/01/04 15:41:52 - WARNING: in operation "replaceSite", example value in response 200 does not validate its schema
2020/01/04 15:41:52 - WARNING: 200.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: definitions.tcp_response_rule.example.type in body should be one of [content inspect-delay]
2020/01/04 15:41:52 - WARNING: definitions.site.farms.items.example.servers.items.example.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: definitions.server.example.max-connections in body is a forbidden property
2020/01/04 15:41:52 - WARNING: definitions.tcp_request_rule.example.type in body should be one of [connection content inspect-delay session]
2020/01/04 15:41:52 - WARNING: definitions.backend.example.httpchk.method in body should be one of [HEAD PUT POST GET TRACE PATCH]
2020/01/04 15:41:52 - WARNING: definitions.backend.example.forwardfor.enabled in body must be of type string: "boolean"
2020/01/04 15:41:52 - WARNING: definitions.backend.example.forwardfor.enabled in body should be one of [enabled]
2020/01/04 15:41:52 - WARNING: definitions.stick_rule.example.type in body should be one of [match on store-request store-response]
akutz commented 4 years ago

After finally getting the Go-Swagger client generated, it's as I remember, Go-Swagger-generated code is...poorly. I prefer the OpenAPI-generated client, even with its issues. It would be nice to get those fixed.

akutz commented 4 years ago

FWIW, the CAPV PR merged using the OpenAPI bindings. They work fine :) https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/pull/705

mjuraga commented 4 years ago

I found what is the issue. It's the way openapi-generator for go handles tags. It creates an api_* file/service for every tag, and in our specification we have multiple tags for some endpoints, for documentation purposes, so these files are created and are surplus:

In go-swagger you can define a list of tags you want to generate, so we avoid generating those "documentation" tags. I didn't find a way to generate only specific tags in openapi-generator. When removing tags "Frotend options", "Backend options" and "HAProxy configuration management" tags, this solves the first issue of duplicate structs.

Maybe we can remove the extra tags from spec, to be inline with all mostly used generators. I just need to check other implications of that.

I am now looking into the issue of pointers to structs now.

mjuraga commented 4 years ago

The second issue is with how the openapi-generator generator client library handles nested objects. Some of those nested objects have required fields, and when they are marshaled into JSON, they are not omitted even if they have the json: omitempty tag.

I think this is more of an issue with the generator then the API, they should have been pointers, so that they are omitted when marshaled into JSON. This way in JSON, you have an empty nested object, but since this object has required fields, the validation on server complains, because required fields are not set.