Skip to content

Commit

Permalink
Generate an Open API Spec instead of go structs (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
joecorall authored Jul 8, 2024
1 parent f980278 commit 5ba1f58
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 51 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
api.yaml
output.json
go-islandora
islandora-starter-site
islandora/*
!islandora/.keep
api/islandora.gen.go
47 changes: 47 additions & 0 deletions api.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
openapi: 3.0.0
info:
version: 1.0.0
title: CSV Validation API
paths:
/upload:
post:
summary: Upload CSV file
requestBody:
required: true
content:
text/csv:
schema:
$ref: '#/components/schemas/IslandoraObject'
responses:
'200':
description: CSV file processed successfully
components:
schemas:
IslandoraObject:
type: object
properties:
{{- range .Fields }}
{{ .MachineName }}:
type: array
title: {{ .Title }}
{{- if .Description }}
description: "{{ .Description }}"
{{- end }}
items:
type: object
properties:
{{- range $k, $v := .OapiProperties }}
{{ $k }}:
type: {{ $v }}
{{- end }}
{{- if .GoType }}
x-go-type: {{ .GoType }}
{{- end }}
{{- if .TypeImport.Path }}
x-go-type-import:
path: {{ .TypeImport.Path }}
{{- if .TypeImport.Name }}
name: {{ .TypeImport.Name }}
{{- end }}
{{- end }}
{{- end }}
5 changes: 5 additions & 0 deletions api/cfg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package: api
generate:
std-http-server: true
models: true
output: islandora.gen.go
3 changes: 3 additions & 0 deletions api/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package api

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../api.yaml
33 changes: 33 additions & 0 deletions api/impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package api

import (
"encoding/json"
"net/http"

islandoraModel "github.com/lehigh-university-libraries/go-islandora/model"
)

// ensure that we've conformed to the `ServerInterface` with a compile-time check
var _ ServerInterface = (*Server)(nil)

type Server struct{}

func NewServer() Server {
return Server{}
}

// (POST /upload)
func (Server) PostUpload(w http.ResponseWriter, r *http.Request) {

// TODO: transform the vanilla CSV into workbench CSV
resp := IslandoraObject{
Title: &islandoraModel.GenericField{
islandoraModel.Generic{
Value: "foo",
},
},
}

w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(resp)
}
4 changes: 2 additions & 2 deletions fixtures/node.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
node_id,langcode,title,published,field_abstract,field_alt_title,field_classification,field_coordinates,field_coordinates_text,field_copyright_date,field_date_captured,field_date_modified,field_date_valid,field_description,field_dewey_classification,field_edition,field_edtf_date,field_edtf_date_created,field_edtf_date_issued,field_extent,field_frequency,field_full_title,field_genre,field_geographic_subject,field_identifier,field_isbn,field_language,field_lcc_classification,field_linked_agent,field_local_identifier,field_member_of,field_mode_of_issuance,field_model,field_note,field_oclc_number,field_physical_form,field_pid,field_place_published,field_place_published_country,field_publisher,field_representative_image,field_resource_type,field_rights,field_subject,field_subject_general,field_subjects_name,field_table_of_contents,field_temporal_subject,field_viewer_override,field_weight
19,,Portrait of J. Crichton-Patterson,1,,,,,,,,,,Portrait of J. Crichton-Patterson holding a computer and a stack of books.,,,,,1999,,,,,,61220/utsc10311,,,,"{""target_id"":906,""rel_type"":""relators:pht""}",,4,,894,,,,,,,University of Toronto Scarborough,,15,"Digital files found in the UTSC Library's Digital Collections are meant for research and private study used in compliance with copyright legislation. Access to digital images and text found on this website and the technical capacity to download or copy it does not imply permission to re-use. Prior written permission to publish, or otherwise use images and text found on the website must be obtained from the copyright holder. Please contact UTSC Library for further information.",,907,,,,34,
Changed,Created,FieldAbstract,FieldAltTitle,FieldClassification,FieldCoordinates,FieldCoordinatesText,FieldCopyrightDate,FieldDateCaptured,FieldDateModified,FieldDateValid,FieldDescription,FieldDeweyClassification,FieldEdition,FieldEdtfDate,FieldEdtfDateCreated,FieldEdtfDateIssued,FieldExtent,FieldFrequency,FieldFullTitle,FieldGenre,FieldGeographicSubject,FieldIdentifier,FieldIsbn,FieldLanguage,FieldLccClassification,FieldLinkedAgent,FieldLocalIdentifier,FieldMemberOf,FieldModeOfIssuance,FieldModel,FieldNote,FieldOclcNumber,FieldPhysicalForm,FieldPid,FieldPlacePublished,FieldPlacePublishedCountry,FieldPublisher,FieldRepresentativeImage,FieldResourceType,FieldRights,FieldSubject,FieldSubjectGeneral,FieldSubjectsName,FieldTableOfContents,FieldTemporalSubject,FieldViewerOverride,FieldWeight,Language,Nid,RevisionLog,RevisionTimestamp,RevisionUid,Status,Title,Type,Uid,Uuid,Vid
2024-06-27T04:05:47+00:00,2024-06-27T04:05:47+00:00,,,,,,,,,,Portrait of J. Crichton-Patterson holding a computer and a stack of books.,,,,,1999,,,,,,61220/utsc10311,,,,"{""target_id"":906,""rel_type"":""relators:pht""}",,4,,894,,,,,,,University of Toronto Scarborough,,15,"Digital files found in the UTSC Library's Digital Collections are meant for research and private study used in compliance with copyright legislation. Access to digital images and text found on this website and the technical capacity to download or copy it does not imply permission to re-use. Prior written permission to publish, or otherwise use images and text found on the website must be obtained from the copyright holder. Please contact UTSC Library for further information.",,907,,,,34,,,19,,2024-06-27T04:05:47+00:00,1,1,Portrait of J. Crichton-Patterson,"[{""target_id"":""islandora_object"",""target_type"":""node_type"",""target_uuid"":""6399fba1-f0d8-4c8a-8162-9c8dd4d357ff""}]",1,63c8ca1b-807f-4774-b9c6-a0d715b31452,19
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ go 1.22.2

require (
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/getkin/kin-openapi v0.124.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
40 changes: 39 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,54 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 h1:rICjNsHbPP1LttefanBPnwsSwl09SqhCO7Ee623qR84=
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0/go.mod h1:4k+cJeSq5ntkwlcpQSxLxICCxQzCL772o30PxdibRt4=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Empty file removed islandora/.keep
Empty file.
134 changes: 122 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,29 @@ import (
)

type Field struct {
Name string
Type string
MachineName string
Name string
Type string
Title string
Description string
MachineName string
Required bool
OapiProperties map[string]string

// see https://github.com/oapi-codegen/oapi-codegen?tab=readme-ov-file#openapi-extensions
GoType string
TypeImport TypeImport
}

type StructData struct {
StructName string
Fields []Field
}

type TypeImport struct {
Path string
Name string
}

func main() {
nodeCexYaml := flag.String("node-cex-yaml", "", "Path to the node CEX YAML file")
output := flag.String("output", "./generated_structs.go", "Output file for generated structs")
Expand Down Expand Up @@ -51,7 +64,7 @@ func main() {
os.Exit(1)
}

fields := []Field{}
fields := nodeFields()

for _, file := range files {
yamlFile, err := os.ReadFile(file)
Expand All @@ -69,12 +82,18 @@ func main() {

fieldName := data["field_name"].(string)
fieldType := data["field_type"].(string)

fieldTypeGo := mapFieldTypeToGoType(fieldType)
fields = append(fields, Field{
Name: toCamelCase(fieldName),
Type: fieldTypeGo,
MachineName: fieldName,
Name: toCamelCase(fieldName),
OapiProperties: mapFieldTypeToOapiProperties(fieldType),
Title: data["label"].(string),
Description: strings.ReplaceAll(data["description"].(string), `"`, `\"`),
MachineName: fieldName,
Required: data["required"].(bool),
GoType: mapFieldTypeToGoType(fieldType),
TypeImport: TypeImport{
Path: "github.com/lehigh-university-libraries/go-islandora/model",
Name: "islandoraModel",
},
})
}

Expand All @@ -84,7 +103,7 @@ func main() {
Fields: fields,
}

structCode, err := generateGoStruct(structData)
structCode, err := generateOapiSpec(structData)
if err != nil {
slog.Error("Error generating Go struct: %s", err)
os.Exit(1)
Expand All @@ -99,8 +118,8 @@ func main() {
slog.Info("Structs generated and written", "file", *output)
}

func generateGoStruct(data StructData) (string, error) {
tmpl, err := template.ParseFiles("node.go.tmpl")
func generateOapiSpec(data StructData) (string, error) {
tmpl, err := template.ParseFiles("api.yaml.tmpl")
if err != nil {
return "", err
}
Expand All @@ -114,6 +133,59 @@ func generateGoStruct(data StructData) (string, error) {
return buf.String(), nil
}

func mapFieldTypeToOapiProperties(fieldType string) map[string]string {
properties := map[string]string{}
switch fieldType {
case "boolean":
properties["value"] = "boolean"
case "entity_reference":
properties["target_id"] = "integer"
case "integer":
properties["value"] = "integer"
case "geolocation":
properties["lat"] = "number"
properties["lng"] = "number"
case "hierarchical_geographic":
properties["continent"] = "string"
properties["country"] = "string"
properties["region"] = "string"
properties["state"] = "string"
properties["territory"] = "string"
properties["county"] = "string"
properties["city"] = "string"
properties["city_section"] = "string"
properties["island"] = "string"
properties["area"] = "string"
properties["extraterrestrial_area"] = "string"
case "typed_relation":
properties["rel_type"] = "string"
properties["target_id"] = "integer"
case "related_item":
properties["identifier"] = "string"
properties["identifier_type"] = "string"
properties["number"] = "string"
properties["title"] = "string"
case "textfield_attr", "textarea_attr":
properties["attr0"] = "string"
properties["attr1"] = "string"
properties["value"] = "string"
if fieldType == "textarea_attr" {
properties["format"] = "string"
}
case "part_detail":
properties["type"] = "string"
properties["caption"] = "string"
properties["number"] = "string"
properties["title"] = "string"
case "config_reference":
properties["target_id"] = "string"
default:
properties["value"] = "string"
}

return properties
}

func mapFieldTypeToGoType(fieldType string) string {
switch fieldType {
case "boolean":
Expand All @@ -138,6 +210,8 @@ func mapFieldTypeToGoType(fieldType string) string {
return "islandoraModel.TypedTextField"
case "part_detail":
return "islandoraModel.PartDetailField"
case "config_reference":
return "islandoraModel.ConfigReferenceField"
default:
return "islandoraModel.GenericField"
}
Expand All @@ -158,3 +232,39 @@ func toCamelCase(input string) string {
}
return output
}

// some base properties for the node entity
func nodeFields() []Field {
fields := []Field{}
f := map[string]string{
"nid": "integer",
"vid": "integer",
"uuid": "string",
"language": "string",
"revision_timestamp": "string",
"revision_uid": "entity_reference",
"revision_log": "string",
"uid": "entity_reference",
"title": "string",
"type": "config_reference",
"status": "boolean",
"created": "string",
"changed": "string",
}
for fieldName, fieldType := range f {
fields = append(fields, Field{
Name: toCamelCase(fieldName),
OapiProperties: mapFieldTypeToOapiProperties(fieldType),
Title: toCamelCase(fieldName),
Description: "",
MachineName: fieldName,
GoType: mapFieldTypeToGoType(fieldType),
TypeImport: TypeImport{
Path: "github.com/lehigh-university-libraries/go-islandora/model",
Name: "islandoraModel",
},
})
}

return fields
}
Loading

0 comments on commit 5ba1f58

Please sign in to comment.