Skip to content

Add endpoints object #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.iws
build
test-project/build
*.swp
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
## Inversoft Client Library Savant Plugin ![semver 2.0.0 compliant](http://img.shields.io/badge/semver-2.0.0-brightgreen.svg?style=flat-square)

This provides objects to the freemarker engine, which turns them into language specific client libraries.

* `apis`. A list of all the json objects pulled from the `src/main/api` directory.
* `domain`. A list of all the json objects pulled from the `src/main/domain` directory. These are generated by the [java2json](https://github.com/inversoft/java2json/) but are checked into the client builder project.
* `camel_to_underscores`. This is a [groovy helper object](https://github.com/inversoft/client-library-plugin/blob/master/src/main/groovy/com/inversoft/savant/plugin/clientLibrary/CamelToUnderscores.groovy) for converting strings from `camelCase` to `snake_case`.
* `endpoints`. This is a map of maps of maps of json objects. `endpoints` is the top level object. Below that is the api endpoint, such as `/api/login`. Below that is a map with the keys of the various methods available for this endpoint. The values of that map are the json objects pulled from the `src/main/api` directory.
4 changes: 2 additions & 2 deletions build.savant
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.nio.file.Paths
*/
savantVersion = "1.0.0"

project(group: "com.inversoft.savant.plugin", name: "client-library", version: "0.3.2", licenses: ["ApacheV2_0"]) {
project(group: "com.inversoft.savant.plugin", name: "client-library", version: "0.3.3", licenses: ["ApacheV2_0"]) {
workflow {
standard()
}
Expand All @@ -34,7 +34,7 @@ project(group: "com.inversoft.savant.plugin", name: "client-library", version: "
}
group(name: "compile") {
dependency(id: "org.savantbuild:savant-io:${savantVersion}")
dependency(id: "org.freemarker:freemarker:2.3.28")
dependency(id: "org.freemarker:freemarker:2.3.30")
dependency(id: "org.inversoft:java2json:1.3.1")
}
group(name: "test-compile", export: false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import freemarker.template.Configuration
import freemarker.template.Template
import freemarker.template.TemplateException
import groovy.io.FileType
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.internal.LazyMap
import org.inversoft.Java2Json
Expand Down Expand Up @@ -77,15 +78,50 @@ class ClientLibraryPlugin extends BaseGroovyPlugin {
}

def root = [
'apis' : [],
'domain' : [],
'camel_to_underscores': new CamelToUnderscores()
'apis' : [],
'endpoints' : [:],
'domain' : [],
'camel_to_underscores' : new CamelToUnderscores()
]
def jsonSlurper = new JsonSlurper()
def files = []
settings.jsonDirectory.eachFile(FileType.FILES) { files << it }
files.sort().each { f ->
root['apis'] << jsonSlurper.parse(f.toFile())
def json = jsonSlurper.parse(f.toFile())
root['apis'] << json

// gather up json by endpoint/http method so that we can build openapi file
// most endpoints only have one option, but some have two, with and without a param
def endpoints = buildOpenapiUri(json.uri, json.params)
def normalEndpoint = endpoints["normal"]
def endpointWithOptionalParam = endpoints["withOptionalParam"]
def http_method = json.method

if (!root['endpoints'][normalEndpoint]) {
root['endpoints'][normalEndpoint] = [:]
}
def onlyNormal = endpointWithOptionalParam == null
if (onlyNormal) {
// handle normal case
root['endpoints'][normalEndpoint][http_method] = json

} else {
// handle case with param
if (!root['endpoints'][endpointWithOptionalParam]) {
root['endpoints'][endpointWithOptionalParam] = [:]
}
root['endpoints'][endpointWithOptionalParam][http_method] = json

// handle with optional params

def optionalUrlSegment = json.params.find { it.required != null && it.required == false && it.type == "urlSegment" }

// remove the optional param from a copy. the normal endpoint doesn't get the optional segment param
def modifiable_json = jsonSlurper.parseText(JsonOutput.toJson(json))
modifiable_json.params = modifiable_json.params - optionalUrlSegment
modifiable_json.methodName = modifiable_json.methodName + "WithoutId"
root['endpoints'][normalEndpoint][http_method] = modifiable_json
}
}

if (settings.domainDirectory.toFile().exists()) {
Expand All @@ -97,6 +133,44 @@ class ClientLibraryPlugin extends BaseGroovyPlugin {
outputFile(FileTools.toPath(parameters['outputFile']), FileTools.toPath(parameters['template']), root, config)
}

//look for urlsegments
Map buildOpenapiUri(uri, params) {
if (!params || params.size == 0) {
return ["normal": uri]
}

def uriSuffix = ""
// TODO this doesn't handle roles which are in the url segment
def constantParams = params.findAll { it.constant != null && it.constant == true && it.value != null && it.type == "urlParameter" }
if (constantParams && constantParams.size >= 1) {
def first = true
for (Map constantParam : constantParams) {
if (first) {
uriSuffix += "?"
} else {
uriSuffix += "&"
}
uriSuffix = uriSuffix + constantParam.name+"="+constantParam.value
if (first) { first = false;}
}
}

def urlSegments = params.findAll { it.type == "urlSegment" }
if (!urlSegments || urlSegments.size == 0) {
return ["normal": uri + uriSuffix]
}
def optionalUrlSegment = urlSegments.find { it.required != null && it.required == false }
if (optionalUrlSegment == null) {
// only the one url segment, it's required
return ["normal": uri+"/{"+urlSegments[0].name+"}"+uriSuffix]
}
def toReturn = [:]
toReturn["withOptionalParam"] = uri+"/{"+optionalUrlSegment.name+"}" + uriSuffix
toReturn["normal"] = uri + uriSuffix

return toReturn
}

void outputFile(Path outputFile, Path templateFile, root, Configuration config) {
Template template = config.getTemplate(templateFile.toAbsolutePath().toString())
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,72 @@ class ClientLibraryPluginTest {
@BeforeMethod
void beforeMethod() {
output = new SystemOutOutput(true)
}

@Test
void buildClient() {
project = new Project(projectDir.resolve("test-project-tomcat"), output)
project.group = "com.inversoft.cleanspeak"
project.name = "cleanspeak-search-engine"
project.version = new Version("1.0")
project.licenses.put(License.ApacheV2_0, null)
}

@Test
void buildOpenapiParamsSimple() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = []
assert plugin.buildOpenapiUri(uri,params) == ["normal":"abc"]
}

@Test
void buildOpenapiParamsWithConstantParam() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlParameter", "name":"foo", "constant":true, "value":"true"]]
assert plugin.buildOpenapiUri(uri,params) == ["normal":"abc?foo=true"]
}

@Test
void buildOpenapiParamsWithParamsNoSegment() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlParameter", "name":"foo"]]
assert plugin.buildOpenapiUri(uri,params) == ["normal":"abc"]
}

@Test
void buildOpenapiParamsWithParamsSegmentRequired() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlSegment", "name":"foo","required":true]]
assert plugin.buildOpenapiUri(uri,params) == ["normal":"abc/{foo}"]
}

@Test
void buildOpenapiParamsWithConstantParamAndSegmentRequired() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlSegment", "name":"foo","required":true],["type": "urlParameter", "name":"bar", "constant":true, "value":"true"]]
assert plugin.buildOpenapiUri(uri,params) == ["normal":"abc/{foo}?bar=true"]
}


@Test
void buildOpenapiParamsWithParamsSegmentOptional() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlSegment", "name":"foo","required":false]]
assert plugin.buildOpenapiUri(uri,params) == ["withOptionalParam": "abc/{foo}", "normal":"abc"]
}

@Test
void buildOpenapiParamsWithMultParamsSegmentOptional() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
def uri = "abc"
def params = [["type": "urlSegment", "name":"foo","required":false], ["type": "urlParameter", "name":"bar"]]
assert plugin.buildOpenapiUri(uri,params) == ["withOptionalParam": "abc/{foo}", "normal":"abc"]
}

@Test
void buildClient() {
ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
plugin.settings.debug = true
plugin.settings.jsonDirectory = Paths.get("src/test/api")
Expand All @@ -66,12 +122,6 @@ class ClientLibraryPluginTest {

@Test
void buildDomain() {
project = new Project(projectDir.resolve("test-project-tomcat"), output)
project.group = "com.inversoft.cleanspeak"
project.name = "cleanspeak-search-engine"
project.version = new Version("1.0")
project.licenses.put(License.ApacheV2_0, null)

ClientLibraryPlugin plugin = new ClientLibraryPlugin(project, new RuntimeConfiguration(), output)
plugin.settings.debug = true
plugin.settings.jsonDirectory = Paths.get("src/test/api")
Expand Down