Skip to content

Commit

Permalink
Restructuring (#8)
Browse files Browse the repository at this point in the history
- Almost complete rewrite using generics
- Adds loading with `$ref` resolution
- Adds validation
  • Loading branch information
chanced authored Sep 22, 2022
1 parent a8e1514 commit 420c91b
Show file tree
Hide file tree
Showing 151 changed files with 16,430 additions and 5,780 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

__debug_bin
# Dependency directories (remove the comment below to include it)
# vendor/
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"staticcheck": true,
},
"files.exclude": {
".git/**": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
Expand All @@ -53,4 +54,7 @@
"tabled": true,
"tabled/**": true
},
"yaml.schemas": {
"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.json": "file:///Users/chance/dev/openapi/testdata/documents/dynamicrefs.yaml"
},
}
151 changes: 123 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,136 @@
# openapi
# openapi - an OpenAPI 3.x library for Go

Package openapi is a set of Go types for [OpenAPI Specification
3.1](https://spec.openapis.org/oas/v3.1.0). The primary purpose of the package
is to assist in generation of OpenAPI documentation or to offer building blocks
for code-generation.
openapi is a library for for OpenAPI 3.x ([3.1](https://spec.openapis.org/oas/v3.1.0),
[3.0](https://spec.openapis.org/oas/v3.0.3)).

## Documentation
The primary purpose of the package is to offer building blocks for code and
documentation generation.

[Documentation can be found on pkg.go.dev](https://pkg.go.dev/github.com/chanced/openapi).
:warning: This library is in an alpha state; expect breaking changes and bugs.

## Features

- `$ref` resolution
- All keys retain their order from the markup using slices of key/values which
aids with code generation.
- Validation ([see the validation seciton](#validation))
- All non-primitive nodes have an absolute & relative location
- Strings are [text.Text](https://github.com/chanced/caps) which has case
conversions and `strings` functions as methods.
- Extensions, unknown JSON Schema keywords, examples, and a few other fields
are instances of [jsonx.RawMessage](https://github.com/chanced/jsonx) which
comes with a few helper methods.
- Supports both JSON and YAML

## Issues

- **Testing.** The code coverage is abysmal at the moment. As I find time, I'll add coverage.
- **`$dynamicRef` / `$dynamicAnchor`** is not really supported. While the
references are loaded, the dynamic overriding is not. I simply have no idea
how to solve it. If you have ideas, I'd really like to hear them.
- **Validation.** [See the Validation section](#validation).
- **Errors.** Errors and error messages need a lot of work.
- [jsonpointer](https://github.com/chanced/jsonpointer)'s Resolve, Assign, and
Delete do not currently work. I need to update the jsonpointer library
before its interfaces can be implemented for types within this library.
- Values of `$anchor` and `$dynamicAnchor` must be unique to a file.
Conditional `$dynamicAnchor` `$recursiveAnchor` are going to be challenging.
See below.
- `$dynamicRef` and `$recursiveRef` are incredibly tricky with respect to
static analysis, which is what this library was built for. You should avoid
conditional branches with `$dynamicAnchor`s within the same file. If you
need a conditional dynamics, move the branch into its own file and have the
conditional statement reference the branch.

## Usage

```go
package main

import (
"github.com/chanced/openapi"
"github.com/chanced/uri"
"github.com/santhosh-tekuri/jsonschema/v5"
"embed"
"io"
"path/filepath"
"log"
)

//go:embed spec
var spec embed.FS

func main() {
ctx := context.Background()

c, err := openapi.SetupCompiler(jsonschema.NewCompiler()) // adding schema files
if err != nil {
log.Fatal(err)
}
v, err := openapi.NewValidator(c)
if err != nil {
log.Fatal(err)
}

fn := func(_ context.Context, uri uri.URI, kind openapi.Kind) (openapi.Kind, []byte, error){
f, err := schema.Open(fp)
if err != nil {
log.Fatal(err)
}
// you can return either JSON or YAML
d, err := io.ReadAll(f)
if err != nil{
log.fatal(err)
}
// use the uri or the data to determine the Kind
return openapi.KindDocument, d, nil
}
// you can Load either JSON or YAML
// Load validates the Document as well.
doc, err := openapi.Load(ctx, "spec/openapi.yaml", v, fn)
if err != nil{
log.Fatal(err)
}
_ = doc // *openapi.Document
}
```

## Validation

Currently, specifications are validated with JSON Schema. Per OpenAPI's
documentation, this may not be enough to properly encapsulate all the nuances
of a specification. However, JSON Schema is able to properly validate the current
OpenAPI 3.1 Specification test suite.
The standard validator (`StdValidator`) currently validates OpenAPI documents
with JSON Schema. Per OpenAPI's documentation, this may not be enough to
properly encapsulate all the nuances of a specification. However, JSON Schema is
able to successfully validate the current OpenAPI 3.1 Specification test suite.

Validation something that needs work. If you have an edge case that is not
covered, you can implement your own Validator either by wrapping `StdValidator`
or simply creating your own.

If you do find cases where the current validator is not sufficient, please open
an issue so that the library can be updated with proper coverage in the future.

Regarding JSON Schema, as of writing this, the only library able to support JSON
Schema 2020-12 is
[github.com/santhosh-tekuri/jsonschema](https://github.com/santhosh-tekuri/jsonschema)
and so the `Compiler`'s interface was modeled after its API. If you would like
to use a different implementation of JSON Schema with the `StdValidator` the
interfaces you need to write an adapter for are:

```go
type Compiler interface {
AddResource(id string, r io.Reader) error
Compile(url string) (CompiledSchema, error)
}

Please open an issue if you run into an edge case that is not validated adequately.
type CompiledSchema interface {
Validate(data interface{}) error
}
```

## Contributions

Please feel free to open up an issue or create a pull request if there are features
you'd like to contribute or issues.

## Dependencies

- [github.com/santhosh-tekuri/jsonschema/v5](https://github.com/santhosh-tekuri/jsonschema/v5) (used for json schema validation)
- [github.com/evanphx/json-patch/v5](https://github.com/evanphx/json-patch/v5) (used for testing purposes)
- [github.com/stretchr/testify](https://github.com/stretchr/testify) (testing)
- [github.com/tidwall/gjson](https://github.com/tidwall/gjson) (json parsing)
- [github.com/tidwall/sjson](https://github.com/tidwall/sjson) (json manipulation)
- [github.com/wI2L/jsondiff](https://github.com/wI2L/jsondiff) (testing purposes)
- [gopkg.in/yaml.v2](https://github.com/wI2L/jsondiff) (yaml)
- [sigs.k8s.io/yaml](https://sigs.k8s.io/yaml) (yaml)
- [github.com/chanced/cmpjson](https://github.com/chanced/cmpjson) (testing purposes)
- [github.com/chanced/dynamic](https://github.com/chanced/dynamic) (json parsing)
- [github.com/pkg/errors](https://github.com/pkg/errors) (errors)
Please feel free to open up an issue or create a pull request if you find issues
or if there are features you'd like to see added.

## License

Expand Down
70 changes: 70 additions & 0 deletions anchor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package openapi

import "fmt"

type DuplicateAnchorError struct {
A *Anchor
B *Anchor
}

func (dae *DuplicateAnchorError) Error() string {
return fmt.Sprintf("duplicate anchor: %s", dae.A.Name)
}

type AnchorType uint8

const (
AnchorTypeUndefined AnchorType = iota
AnchorTypeRegular // $anchor
AnchorTypeRecursive // $recursiveAnchor
AnchorTypeDynamic // $dynamicAnchor
)

type Anchor struct {
Location
In *Schema
Name Text
Type AnchorType
}

type Anchors struct {
Standard map[Text]Anchor // $anchor
Recursive *Anchor // $recursiveAnchor
Dynamic map[Text]Anchor // $dynamicAnchor
}

func (a *Anchors) merge(b *Anchors, err error) (*Anchors, error) {
if err != nil {
return nil, err
}
if b == nil {
return a, nil
}

// we do not merge recursive anchors as they must be at the root of the
// document. This method is only called when merging schemas from nested
// components, so we can, and should, drop them from result if not coming
// from a.

if a == nil {
return &Anchors{
Standard: b.Standard,
Dynamic: b.Dynamic,
}, nil
}
for k, bv := range b.Standard {
if av, ok := a.Standard[k]; ok {
return nil, &DuplicateAnchorError{&av, &bv}
}
a.Standard[k] = bv
}

for k, bv := range b.Dynamic {
if av, ok := a.Dynamic[k]; ok {
return nil, &DuplicateAnchorError{&av, &bv}
}
a.Dynamic[k] = bv
}

return a, nil
}
131 changes: 0 additions & 131 deletions callback.go

This file was deleted.

Loading

0 comments on commit 420c91b

Please sign in to comment.