Skip to content

Commit 420c91b

Browse files
authored
Restructuring (#8)
- Almost complete rewrite using generics - Adds loading with `$ref` resolution - Adds validation
1 parent a8e1514 commit 420c91b

File tree

151 files changed

+16430
-5780
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+16430
-5780
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13-
13+
__debug_bin
1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/

.vscode/settings.json

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"staticcheck": true,
4545
},
4646
"files.exclude": {
47+
".git/**": true,
4748
"**/.git": true,
4849
"**/.svn": true,
4950
"**/.hg": true,
@@ -53,4 +54,7 @@
5354
"tabled": true,
5455
"tabled/**": true
5556
},
57+
"yaml.schemas": {
58+
"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.json": "file:///Users/chance/dev/openapi/testdata/documents/dynamicrefs.yaml"
59+
},
5660
}

README.md

+123-28
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,136 @@
1-
# openapi
1+
# openapi - an OpenAPI 3.x library for Go
22

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

8-
## Documentation
6+
The primary purpose of the package is to offer building blocks for code and
7+
documentation generation.
98

10-
[Documentation can be found on pkg.go.dev](https://pkg.go.dev/github.com/chanced/openapi).
9+
:warning: This library is in an alpha state; expect breaking changes and bugs.
10+
11+
## Features
12+
13+
- `$ref` resolution
14+
- All keys retain their order from the markup using slices of key/values which
15+
aids with code generation.
16+
- Validation ([see the validation seciton](#validation))
17+
- All non-primitive nodes have an absolute & relative location
18+
- Strings are [text.Text](https://github.com/chanced/caps) which has case
19+
conversions and `strings` functions as methods.
20+
- Extensions, unknown JSON Schema keywords, examples, and a few other fields
21+
are instances of [jsonx.RawMessage](https://github.com/chanced/jsonx) which
22+
comes with a few helper methods.
23+
- Supports both JSON and YAML
24+
25+
## Issues
26+
27+
- **Testing.** The code coverage is abysmal at the moment. As I find time, I'll add coverage.
28+
- **`$dynamicRef` / `$dynamicAnchor`** is not really supported. While the
29+
references are loaded, the dynamic overriding is not. I simply have no idea
30+
how to solve it. If you have ideas, I'd really like to hear them.
31+
- **Validation.** [See the Validation section](#validation).
32+
- **Errors.** Errors and error messages need a lot of work.
33+
- [jsonpointer](https://github.com/chanced/jsonpointer)'s Resolve, Assign, and
34+
Delete do not currently work. I need to update the jsonpointer library
35+
before its interfaces can be implemented for types within this library.
36+
- Values of `$anchor` and `$dynamicAnchor` must be unique to a file.
37+
Conditional `$dynamicAnchor` `$recursiveAnchor` are going to be challenging.
38+
See below.
39+
- `$dynamicRef` and `$recursiveRef` are incredibly tricky with respect to
40+
static analysis, which is what this library was built for. You should avoid
41+
conditional branches with `$dynamicAnchor`s within the same file. If you
42+
need a conditional dynamics, move the branch into its own file and have the
43+
conditional statement reference the branch.
44+
45+
## Usage
46+
47+
```go
48+
package main
49+
50+
import (
51+
"github.com/chanced/openapi"
52+
"github.com/chanced/uri"
53+
"github.com/santhosh-tekuri/jsonschema/v5"
54+
"embed"
55+
"io"
56+
"path/filepath"
57+
"log"
58+
)
59+
60+
//go:embed spec
61+
var spec embed.FS
62+
63+
func main() {
64+
ctx := context.Background()
65+
66+
c, err := openapi.SetupCompiler(jsonschema.NewCompiler()) // adding schema files
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
v, err := openapi.NewValidator(c)
71+
if err != nil {
72+
log.Fatal(err)
73+
}
74+
75+
fn := func(_ context.Context, uri uri.URI, kind openapi.Kind) (openapi.Kind, []byte, error){
76+
f, err := schema.Open(fp)
77+
if err != nil {
78+
log.Fatal(err)
79+
}
80+
// you can return either JSON or YAML
81+
d, err := io.ReadAll(f)
82+
if err != nil{
83+
log.fatal(err)
84+
}
85+
// use the uri or the data to determine the Kind
86+
return openapi.KindDocument, d, nil
87+
}
88+
// you can Load either JSON or YAML
89+
// Load validates the Document as well.
90+
doc, err := openapi.Load(ctx, "spec/openapi.yaml", v, fn)
91+
if err != nil{
92+
log.Fatal(err)
93+
}
94+
_ = doc // *openapi.Document
95+
}
96+
```
1197

1298
## Validation
1399

14-
Currently, specifications are validated with JSON Schema. Per OpenAPI's
15-
documentation, this may not be enough to properly encapsulate all the nuances
16-
of a specification. However, JSON Schema is able to properly validate the current
17-
OpenAPI 3.1 Specification test suite.
100+
The standard validator (`StdValidator`) currently validates OpenAPI documents
101+
with JSON Schema. Per OpenAPI's documentation, this may not be enough to
102+
properly encapsulate all the nuances of a specification. However, JSON Schema is
103+
able to successfully validate the current OpenAPI 3.1 Specification test suite.
104+
105+
Validation something that needs work. If you have an edge case that is not
106+
covered, you can implement your own Validator either by wrapping `StdValidator`
107+
or simply creating your own.
108+
109+
If you do find cases where the current validator is not sufficient, please open
110+
an issue so that the library can be updated with proper coverage in the future.
111+
112+
Regarding JSON Schema, as of writing this, the only library able to support JSON
113+
Schema 2020-12 is
114+
[github.com/santhosh-tekuri/jsonschema](https://github.com/santhosh-tekuri/jsonschema)
115+
and so the `Compiler`'s interface was modeled after its API. If you would like
116+
to use a different implementation of JSON Schema with the `StdValidator` the
117+
interfaces you need to write an adapter for are:
118+
119+
```go
120+
type Compiler interface {
121+
AddResource(id string, r io.Reader) error
122+
Compile(url string) (CompiledSchema, error)
123+
}
18124

19-
Please open an issue if you run into an edge case that is not validated adequately.
125+
type CompiledSchema interface {
126+
Validate(data interface{}) error
127+
}
128+
```
20129

21130
## Contributions
22131

23-
Please feel free to open up an issue or create a pull request if there are features
24-
you'd like to contribute or issues.
25-
26-
## Dependencies
27-
28-
- [github.com/santhosh-tekuri/jsonschema/v5](https://github.com/santhosh-tekuri/jsonschema/v5) (used for json schema validation)
29-
- [github.com/evanphx/json-patch/v5](https://github.com/evanphx/json-patch/v5) (used for testing purposes)
30-
- [github.com/stretchr/testify](https://github.com/stretchr/testify) (testing)
31-
- [github.com/tidwall/gjson](https://github.com/tidwall/gjson) (json parsing)
32-
- [github.com/tidwall/sjson](https://github.com/tidwall/sjson) (json manipulation)
33-
- [github.com/wI2L/jsondiff](https://github.com/wI2L/jsondiff) (testing purposes)
34-
- [gopkg.in/yaml.v2](https://github.com/wI2L/jsondiff) (yaml)
35-
- [sigs.k8s.io/yaml](https://sigs.k8s.io/yaml) (yaml)
36-
- [github.com/chanced/cmpjson](https://github.com/chanced/cmpjson) (testing purposes)
37-
- [github.com/chanced/dynamic](https://github.com/chanced/dynamic) (json parsing)
38-
- [github.com/pkg/errors](https://github.com/pkg/errors) (errors)
132+
Please feel free to open up an issue or create a pull request if you find issues
133+
or if there are features you'd like to see added.
39134

40135
## License
41136

anchor.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package openapi
2+
3+
import "fmt"
4+
5+
type DuplicateAnchorError struct {
6+
A *Anchor
7+
B *Anchor
8+
}
9+
10+
func (dae *DuplicateAnchorError) Error() string {
11+
return fmt.Sprintf("duplicate anchor: %s", dae.A.Name)
12+
}
13+
14+
type AnchorType uint8
15+
16+
const (
17+
AnchorTypeUndefined AnchorType = iota
18+
AnchorTypeRegular // $anchor
19+
AnchorTypeRecursive // $recursiveAnchor
20+
AnchorTypeDynamic // $dynamicAnchor
21+
)
22+
23+
type Anchor struct {
24+
Location
25+
In *Schema
26+
Name Text
27+
Type AnchorType
28+
}
29+
30+
type Anchors struct {
31+
Standard map[Text]Anchor // $anchor
32+
Recursive *Anchor // $recursiveAnchor
33+
Dynamic map[Text]Anchor // $dynamicAnchor
34+
}
35+
36+
func (a *Anchors) merge(b *Anchors, err error) (*Anchors, error) {
37+
if err != nil {
38+
return nil, err
39+
}
40+
if b == nil {
41+
return a, nil
42+
}
43+
44+
// we do not merge recursive anchors as they must be at the root of the
45+
// document. This method is only called when merging schemas from nested
46+
// components, so we can, and should, drop them from result if not coming
47+
// from a.
48+
49+
if a == nil {
50+
return &Anchors{
51+
Standard: b.Standard,
52+
Dynamic: b.Dynamic,
53+
}, nil
54+
}
55+
for k, bv := range b.Standard {
56+
if av, ok := a.Standard[k]; ok {
57+
return nil, &DuplicateAnchorError{&av, &bv}
58+
}
59+
a.Standard[k] = bv
60+
}
61+
62+
for k, bv := range b.Dynamic {
63+
if av, ok := a.Dynamic[k]; ok {
64+
return nil, &DuplicateAnchorError{&av, &bv}
65+
}
66+
a.Dynamic[k] = bv
67+
}
68+
69+
return a, nil
70+
}

callback.go

-131
This file was deleted.

0 commit comments

Comments
 (0)