LS6-Events / astra

An automatic Go type extractor for web services with very little additional configuration.
MIT License
31 stars 3 forks source link

Temperamental documentation of external types #34

Closed TommoLeedsy closed 9 months ago

TommoLeedsy commented 10 months ago

Issue Description

Astra can be temperamental when extracting documentation for external types, sometimes they will all be documented perfectly, some will be documented or none at all will be.

Steps to Reproduce

  1. Create a main.go with this content:
    
    package main

import ( "fmt" "net/http" "time"

"github.com/gin-gonic/gin"
"github.com/ls6-events/astra"
"github.com/ls6-events/astra/inputs"
"github.com/ls6-events/astra/outputs"

)

type TimeResponse struct { NewTime time.Time json:"new_time" }

func main() { r := gin.Default()

r.GET("/addtime/:duration", func(c *gin.Context) {
    // Get the duration parameter from the path
    durationStr := c.Param("duration")

    // Parse the duration string to time.Duration
    duration, err := time.ParseDuration(durationStr)
    if err != nil {
        // Handle the error and return a JSON response
        c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid duration: %s", err.Error())})
        return
    }

    // Add the duration to the current time
    resultTime := time.Now().Add(duration)

    // Return the result in the response
    response := TimeResponse{
        NewTime: resultTime,
    }

    c.JSON(http.StatusOK, response)
})

gen := astra.New(inputs.WithGinInput(r), outputs.WithOpenAPIOutput("openapi.generated.yaml"))

config := astra.Config{
    Title:   "Example API",
    Version: "1.0.0",
    Host:    "localhost",
    Port:    8080,
}

gen.SetConfig(&config)

err := gen.Parse()
if err != nil {
    panic(err)
}

// Run the server on port 8080
r.Run(":8080")

}


2. Install dependencies: `go get .`
3. Run `main.go`: `go run main.go`

## Expected Behaviour

Bellow is the expected output in `openapi.generated.yaml` from running `main.go`.

```yaml
openapi: 3.0.0
info:
    title: Example API
    description: Generated by astra
    contact: {}
    license:
        name: ""
    version: 1.0.0
servers:
    - url: http://localhost:8080
paths:
    /addtime/{duration}:
        get:
            parameters:
                - name: duration
                  in: path
                  required: true
                  schema:
                    type: string
            responses:
                "200":
                    description: ""
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/main.TimeResponse'
                "400":
                    description: ""
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/gin.H'
components:
    schemas:
        gin.H:
            type: object
            additionalProperties: {}
            description: H is a shortcut for map[string]any
        main.TimeResponse:
            type: object
            properties:
                new_time:
                    $ref: '#/components/schemas/time.Time'
        time.Time:
            type: string
            format: date-time
            description: |-
                A Time represents an instant in time with nanosecond precision.

                Programs using times should typically store and pass them as values,
                not pointers. That is, time variables and struct fields should be of
                type time.Time, not *time.Time.

                A Time value can be used by multiple goroutines simultaneously except
                that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and
                UnmarshalText are not concurrency-safe.

                Time instants can be compared using the Before, After, and Equal methods.
                The Sub method subtracts two instants, producing a Duration.
                The Add method adds a Time and a Duration, producing a Time.

                The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
                As this time is unlikely to come up in practice, the IsZero method gives
                a simple way of detecting a time that has not been initialized explicitly.

                Each Time has associated with it a Location, consulted when computing the
                presentation form of the time, such as in the Format, Hour, and Year methods.
                The methods Local, UTC, and In return a Time with a specific location.
                Changing the location in this way changes only the presentation; it does not
                change the instant in time being denoted and therefore does not affect the
                computations described in earlier paragraphs.

                Representations of a Time value saved by the GobEncode, MarshalBinary,
                MarshalJSON, and MarshalText methods store the Time.Location's offset, but not
                the location name. They therefore lose information about Daylight Saving Time.

                In addition to the required “wall clock” reading, a Time may contain an optional
                reading of the current process's monotonic clock, to provide additional precision
                for comparison or subtraction.
                See the “Monotonic Clocks” section in the package documentation for details.

                Note that the Go == operator compares not just the time instant but also the
                Location and the monotonic clock reading. Therefore, Time values should not
                be used as map or database keys without first guaranteeing that the
                identical Location has been set for all values, which can be achieved
                through use of the UTC or Local method, and that the monotonic clock reading
                has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u)
                to t == u, since t.Equal uses the most accurate comparison available and
                correctly handles the case when only one of its arguments has a monotonic
                clock reading.

Actual Behaviour

Bellow is the worst case output in openapi.generated.yaml from running main.go. It can have sometimes have both, either or none of gin.H and time.Time documented but it is very it and miss.

openapi: 3.0.0
info:
    title: Example API
    description: Generated by astra
    contact: {}
    license:
        name: ""
    version: 1.0.0
servers:
    - url: http://localhost:8080
paths:
    /addtime/{duration}:
        get:
            parameters:
                - name: duration
                  in: path
                  required: true
                  schema:
                    type: string
            responses:
                "200":
                    description: ""
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/main.TimeResponse'
                "400":
                    description: ""
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/gin.H'
components:
    schemas:
        gin.H:
            type: object
            additionalProperties: {}
        main.TimeResponse:
            type: object
            properties:
                new_time:
                    $ref: '#/components/schemas/time.Time'
        time.Time:
            type: string
            format: date-time

Additional Information