astahmer / openapi-zod-client

Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)
openapi-zod-client.vercel.app
810 stars 87 forks source link

TODO - Better readme #69

Open astahmer opened 1 year ago

astahmer commented 1 year ago

this was done very quickly when the project was at the proof of concept stage, it hasn't been updated much since and it could benefit from a bit of a presentation effort

astahmer commented 1 year ago

also a "contributing" with a link to changeset could be nice

timohermans commented 1 year ago

Just wanted to give one example of where the docs could be improved 😊. I have a Spring Boot with Spring Data Rest API, which auto-generates all crud endpoints based on the entities you defined in your project. It does this in a HAL format. The downside of this format is that it's very verbose.

So when I tried this library out, all my response types were z.void(), even though the openapi spec was v3. So after just randomly trying the complexity-threshold flag and setting it to 100, it worked 😎.

@astahmer really love the work that is done here, thanks ❤️! It really fills the void when unable to use typescript on the backend.

astahmer commented 1 year ago

hey ! glad it heps ! 🙏

can you provide a minimal repro of that bug ? it seems very weird to me that it's fixed with complexity-threshold 😅 the point of that option is (or should be, apparently) to give somewhat a bit of control on which schema will be inlined or assigned to a variable with a name (and therefore re-used rather than duplicated)

timohermans commented 1 year ago

@astahmer Whoops! You're totally correct, removing the flag and running it again still worked 😅.

There was one other thing that I configured on the API server. By default, by using HAL, the content type by default is application/hal+json, not application/json. When I set the default content type on the server to application/json (even though it's still using HAL), the script works. Maybe that has something to do with it?

The script I used was pnpm dlx openapi-zod-client http://localhost:8080/v3/api-docs -o src/schema.ts, so nothing strange here, I guess. Here is the spec (you can see there's only application/json for the POST/PUT/PATCH, which is indeed the only one that works):

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8080
  description: Generated server url
paths:
  /adventures:
    get:
      tags:
      - adventure-entity-controller
      description: get-adventure
      operationId: getCollectionResource-adventure-get_1
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/CollectionModelEntityModelAdventure'
            application/x-spring-data-compact+json:
              schema:
                $ref: '#/components/schemas/CollectionModelEntityModelAdventure'
            text/uri-list:
              schema:
                type: string
    post:
      tags:
      - adventure-entity-controller
      description: create-adventure
      operationId: postCollectionResource-adventure-post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdventureRequestBody'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/EntityModelAdventure'
  /adventures/search/existsByName:
    get:
      tags:
      - adventure-search-controller
      operationId: executeSearch-adventure-get
      parameters:
      - name: name
        in: query
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                type: boolean
        "404":
          description: Not Found
  /adventures/{id}:
    get:
      tags:
      - adventure-entity-controller
      description: get-adventure
      operationId: getItemResource-adventure-get
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/EntityModelAdventure'
        "404":
          description: Not Found
    put:
      tags:
      - adventure-entity-controller
      description: update-adventure
      operationId: putItemResource-adventure-put
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdventureRequestBody'
        required: true
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/EntityModelAdventure'
        "201":
          description: Created
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/EntityModelAdventure'
        "204":
          description: No Content
    delete:
      tags:
      - adventure-entity-controller
      description: delete-adventure
      operationId: deleteItemResource-adventure-delete
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
        "404":
          description: Not Found
    patch:
      tags:
      - adventure-entity-controller
      description: patch-adventure
      operationId: patchItemResource-adventure-patch
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdventureRequestBody'
        required: true
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/EntityModelAdventure'
        "204":
          description: No Content
  /profile:
    get:
      tags:
      - profile-controller
      operationId: listAllFormsOfMetadata_1
      responses:
        "200":
          description: OK
          content:
            application/hal+json:
              schema:
                $ref: '#/components/schemas/RepresentationModelObject'
  /profile/adventures:
    get:
      tags:
      - profile-controller
      operationId: descriptor_1_1_1
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string
            application/alps+json:
              schema:
                type: string
            application/schema+json:
              schema:
                $ref: '#/components/schemas/JsonSchema'
components:
  schemas:
    AbstractJsonSchemaPropertyObject:
      type: object
      properties:
        title:
          type: string
        readOnly:
          type: boolean
    Item:
      type: object
      properties:
        type:
          type: string
        properties:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/AbstractJsonSchemaPropertyObject'
        requiredProperties:
          type: array
          items:
            type: string
    JsonSchema:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        properties:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/AbstractJsonSchemaPropertyObject'
        requiredProperties:
          type: array
          items:
            type: string
        definitions:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/Item'
        type:
          type: string
        $schema:
          type: string
    Links:
      type: object
      additionalProperties:
        $ref: '#/components/schemas/Link'
    RepresentationModelObject:
      type: object
      properties:
        _links:
          $ref: '#/components/schemas/Links'
    EntityModelAdventure:
      required:
      - name
      - teachingDays
      type: object
      properties:
        name:
          maxLength: 255
          minLength: 3
          type: string
        category:
          maxLength: 255
          minLength: 3
          type: string
        teachingDays:
          type: array
          items:
            type: integer
            format: int32
        externalId:
          type: integer
          format: int32
        _links:
          $ref: '#/components/schemas/Links'
    CollectionModelEntityModelAdventure:
      type: object
      properties:
        _embedded:
          type: object
          properties:
            adventures:
              type: array
              items:
                $ref: '#/components/schemas/EntityModelAdventure'
        _links:
          $ref: '#/components/schemas/Links'
    AdventureRequestBody:
      required:
      - name
      - teachingDays
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          maxLength: 255
          minLength: 3
          type: string
        category:
          maxLength: 255
          minLength: 3
          type: string
        teachingDays:
          type: array
          items:
            type: integer
            format: int32
        externalId:
          type: integer
          format: int32
    Link:
      type: object
      properties:
        href:
          type: string
        hreflang:
          type: string
        title:
          type: string
        type:
          type: string
        deprecation:
          type: string
        profile:
          type: string
        name:
          type: string
        templated:
          type: boolean
astahmer commented 1 year ago

ah yes, makes sense now

this is the option you're looking for then (and this is the default value): Screenshot 2023-01-29 at 19 00 48

--media-type-expr <expr> Pass an expression to determine which response content should be allowed

when used like this pnpx openapi-zod-client ./petstore.yaml -o ./api.client.ts --media-type-expr="mediaType === 'application/hal+json', it works fine

you could also allow both with true or mediaType.includes("json") Screenshot 2023-01-29 at 19 01 43

can try it here

timohermans commented 1 year ago

Thanks! I tried the --media-type-expr, though I didn't know I had to create the expression like that 😳. Sorry for hijacking this issue, and thank you again for creating this. Amazing project!